Revision: 7895
Author:   [email protected]
Date:     Mon May 16 02:06:16 2011
Log: Extend GCMole with poor man's data flow analysis to catch dead raw pointer vars.

Fix various places in the code found by improved GCMole.

Review URL: http://codereview.chromium.org/6973063
http://code.google.com/p/v8/source/detail?r=7895

Added:
 /branches/bleeding_edge/tools/gcmole/gccause.lua
Modified:
 /branches/bleeding_edge/src/arm/macro-assembler-arm.cc
 /branches/bleeding_edge/src/arm/stub-cache-arm.cc
 /branches/bleeding_edge/src/bootstrapper.cc
 /branches/bleeding_edge/src/liveedit.cc
 /branches/bleeding_edge/src/objects.cc
 /branches/bleeding_edge/src/runtime.cc
 /branches/bleeding_edge/src/uri.js
 /branches/bleeding_edge/tools/gcmole/gcmole.cc
 /branches/bleeding_edge/tools/gcmole/gcmole.lua

=======================================
--- /dev/null
+++ /branches/bleeding_edge/tools/gcmole/gccause.lua Mon May 16 02:06:16 2011
@@ -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
=======================================
--- /branches/bleeding_edge/src/arm/macro-assembler-arm.cc Mon May 9 09:15:18 2011 +++ /branches/bleeding_edge/src/arm/macro-assembler-arm.cc Mon May 16 02:06:16 2011
@@ -1757,7 +1757,7 @@
   { MaybeObject* maybe_result = stub->TryGetCode();
     if (!maybe_result->ToObject(&result)) return maybe_result;
   }
-  Jump(stub->GetCode(), RelocInfo::CODE_TARGET, cond);
+  Jump(Handle<Code>(Code::cast(result)), RelocInfo::CODE_TARGET, cond);
   return result;
 }

=======================================
--- /branches/bleeding_edge/src/arm/stub-cache-arm.cc Mon May 9 09:15:18 2011 +++ /branches/bleeding_edge/src/arm/stub-cache-arm.cc Mon May 16 02:06:16 2011
@@ -3528,7 +3528,8 @@
       __ Ret();
     } else {
       WriteInt32ToHeapNumberStub stub(value, r0, r3);
-      __ TailCallStub(&stub);
+      MaybeObject* stub_code = masm()->TryTailCallStub(&stub);
+      if (stub_code->IsFailure()) return stub_code;
     }
   } else if (array_type == kExternalUnsignedIntArray) {
     // The test is different for unsigned int values. Since we need
=======================================
--- /branches/bleeding_edge/src/bootstrapper.cc Fri May 13 03:58:25 2011
+++ /branches/bleeding_edge/src/bootstrapper.cc Mon May 16 02:06:16 2011
@@ -1701,14 +1701,14 @@
   F(16, global_context()->regexp_function())


-static FixedArray* CreateCache(int size, JSFunction* factory_function) {
+static FixedArray* CreateCache(int size, Handle<JSFunction> factory_function) {
   Factory* factory = factory_function->GetIsolate()->factory();
   // Caches are supposed to live for a long time, allocate in old space.
   int array_size = JSFunctionResultCache::kEntriesIndex + 2 * size;
   // Cannot use cast as object is not fully initialized yet.
   JSFunctionResultCache* cache = reinterpret_cast<JSFunctionResultCache*>(
       *factory->NewFixedArrayWithHoles(array_size, TENURED));
-  cache->set(JSFunctionResultCache::kFactoryIndex, factory_function);
+  cache->set(JSFunctionResultCache::kFactoryIndex, *factory_function);
   cache->MakeZeroSize();
   return cache;
 }
@@ -1725,9 +1725,9 @@

   int index = 0;

-#define F(size, func) do {                           \
-    FixedArray* cache = CreateCache((size), (func)); \
-    caches->set(index++, cache);                     \
+#define F(size, func) do {                                              \
+    FixedArray* cache = CreateCache((size), Handle<JSFunction>(func));  \
+    caches->set(index++, cache);                                        \
   } while (false)

   JSFUNCTION_RESULT_CACHE_LIST(F);
=======================================
--- /branches/bleeding_edge/src/liveedit.cc     Tue May  3 01:23:58 2011
+++ /branches/bleeding_edge/src/liveedit.cc     Mon May 16 02:06:16 2011
@@ -527,12 +527,12 @@

 // Wraps any object into a OpaqueReference, that will hide the object
 // from JavaScript.
-static Handle<JSValue> WrapInJSValue(Object* object) {
+static Handle<JSValue> WrapInJSValue(Handle<Object> object) {
   Handle<JSFunction> constructor =
       Isolate::Current()->opaque_reference_function();
   Handle<JSValue> result =
       Handle<JSValue>::cast(FACTORY->NewJSObject(constructor));
-  result->set_value(object);
+  result->set_value(*object);
   return result;
 }

@@ -599,17 +599,17 @@
   }
   void SetFunctionCode(Handle<Code> function_code,
       Handle<Object> code_scope_info) {
-    Handle<JSValue> code_wrapper = WrapInJSValue(*function_code);
+    Handle<JSValue> code_wrapper = WrapInJSValue(function_code);
     this->SetField(kCodeOffset_, code_wrapper);

-    Handle<JSValue> scope_wrapper = WrapInJSValue(*code_scope_info);
+    Handle<JSValue> scope_wrapper = WrapInJSValue(code_scope_info);
     this->SetField(kCodeScopeInfoOffset_, scope_wrapper);
   }
   void SetOuterScopeInfo(Handle<Object> scope_info_array) {
     this->SetField(kOuterScopeInfoOffset_, scope_info_array);
   }
   void SetSharedFunctionInfo(Handle<SharedFunctionInfo> info) {
-    Handle<JSValue> info_holder = WrapInJSValue(*info);
+    Handle<JSValue> info_holder = WrapInJSValue(info);
     this->SetField(kSharedFunctionInfoOffset_, info_holder);
   }
   int GetParentIndex() {
@@ -666,7 +666,7 @@
                      Handle<SharedFunctionInfo> info) {
     HandleScope scope;
     this->SetField(kFunctionNameOffset_, name);
-    Handle<JSValue> info_holder = WrapInJSValue(*info);
+    Handle<JSValue> info_holder = WrapInJSValue(info);
     this->SetField(kSharedInfoOffset_, info_holder);
     this->SetSmiValueField(kStartPositionOffset_, start_position);
     this->SetSmiValueField(kEndPositionOffset_, end_position);
=======================================
--- /branches/bleeding_edge/src/objects.cc      Fri May 13 03:58:25 2011
+++ /branches/bleeding_edge/src/objects.cc      Mon May 16 02:06:16 2011
@@ -2873,8 +2873,9 @@
// exception. dictionary->DeleteProperty will return false_value()
           // if a non-configurable property is being deleted.
           HandleScope scope;
+          Handle<Object> self(this);
           Handle<Object> i = isolate->factory()->NewNumberFromUint(index);
-          Handle<Object> args[2] = { i, Handle<Object>(this) };
+          Handle<Object> args[2] = { i, self };
           return isolate->Throw(*isolate->factory()->NewTypeError(
               "strict_delete_property", HandleVector(args, 2)));
         }
@@ -7292,7 +7293,7 @@

   // api style callbacks.
   if (structure->IsAccessorInfo()) {
-    AccessorInfo* data = AccessorInfo::cast(structure);
+    Handle<AccessorInfo> data(AccessorInfo::cast(structure));
     Object* fun_obj = data->getter();
     v8::AccessorGetter call_fun = v8::ToCData<v8::AccessorGetter>(fun_obj);
     HandleScope scope(isolate);
@@ -7349,14 +7350,16 @@

   if (structure->IsAccessorInfo()) {
     // api style callbacks
-    AccessorInfo* data = AccessorInfo::cast(structure);
+    Handle<JSObject> self(this);
+    Handle<JSObject> holder_handle(JSObject::cast(holder));
+    Handle<AccessorInfo> data(AccessorInfo::cast(structure));
     Object* call_obj = data->setter();
v8::AccessorSetter call_fun = v8::ToCData<v8::AccessorSetter>(call_obj);
     if (call_fun == NULL) return value;
     Handle<Object> number = isolate->factory()->NewNumberFromUint(index);
     Handle<String> key(isolate->factory()->NumberToString(number));
-    LOG(isolate, ApiNamedPropertyAccess("store", this, *key));
- CustomArguments args(isolate, data->data(), this, JSObject::cast(holder));
+    LOG(isolate, ApiNamedPropertyAccess("store", *self, *key));
+    CustomArguments args(isolate, data->data(), *self, *holder_handle);
     v8::AccessorInfo info(args.end());
     {
       // Leaving JavaScript.
@@ -7558,8 +7561,8 @@
           // If put fails instrict mode, throw exception.
           if (!dictionary->ValueAtPut(entry, value) &&
               strict_mode == kStrictMode) {
- Handle<Object> number(isolate->factory()->NewNumberFromUint(index));
             Handle<Object> holder(this);
+ Handle<Object> number(isolate->factory()->NewNumberFromUint(index));
             Handle<Object> args[2] = { number, holder };
             return isolate->Throw(
*isolate->factory()->NewTypeError("strict_read_only_property",
=======================================
--- /branches/bleeding_edge/src/runtime.cc      Fri May 13 03:58:25 2011
+++ /branches/bleeding_edge/src/runtime.cc      Mon May 16 02:06:16 2011
@@ -7928,12 +7928,13 @@
     // If the "property" we were looking for is a local variable or an
     // argument in a context, the receiver is the global object; see
     // ECMA-262, 3rd., 10.1.6 and 10.2.3.
-    JSObject* receiver =
-        isolate->context()->global()->global_receiver();
+    // GetElement below can cause GC.
+    Handle<JSObject> receiver(
+        isolate->context()->global()->global_receiver());
     MaybeObject* value = (holder->IsContext())
         ? Context::cast(*holder)->get(index)
         : JSObject::cast(*holder)->GetElement(index);
-    return MakePair(Unhole(isolate->heap(), value, attributes), receiver);
+    return MakePair(Unhole(isolate->heap(), value, attributes), *receiver);
   }

   // If the holder is found, we read the property from it.
@@ -7948,10 +7949,14 @@
     } else {
       receiver = ComputeReceiverForNonGlobal(isolate, object);
     }
+
+    // GetProperty below can cause GC.
+    Handle<JSObject> receiver_handle(receiver);
+
     // No need to unhole the value here. This is taken care of by the
     // GetProperty function.
     MaybeObject* value = object->GetProperty(*name);
-    return MakePair(value, receiver);
+    return MakePair(value, *receiver_handle);
   }

   if (throw_error) {
=======================================
--- /branches/bleeding_edge/src/uri.js  Tue Feb  8 05:01:34 2011
+++ /branches/bleeding_edge/src/uri.js  Mon May 16 02:06:16 2011
@@ -166,7 +166,10 @@
 // ECMA-262, section 15.1.3
 function Encode(uri, unescape) {
   var uriLength = uri.length;
-  var result = new $Array(uriLength);
+  // We are going to pass result to %StringFromCharCodeArray
+  // which does not expect any getters/setters installed
+  // on the incoming array.
+  var result = new InternalArray(uriLength);
   var index = 0;
   for (var k = 0; k < uriLength; k++) {
     var cc1 = uri.charCodeAt(k);
@@ -192,7 +195,10 @@
 // ECMA-262, section 15.1.3
 function Decode(uri, reserved) {
   var uriLength = uri.length;
-  var result = new $Array(uriLength);
+  // We are going to pass result to %StringFromCharCodeArray
+  // which does not expect any getters/setters installed
+  // on the incoming array.
+  var result = new InternalArray(uriLength);
   var index = 0;
   for (var k = 0; k < uriLength; k++) {
     var ch = uri.charAt(k);
=======================================
--- /branches/bleeding_edge/tools/gcmole/gcmole.cc      Thu Apr  7 05:38:16 2011
+++ /branches/bleeding_edge/tools/gcmole/gcmole.cc      Mon May 16 02:06:16 2011
@@ -69,6 +69,47 @@
 }


+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) {
@@ -140,12 +181,14 @@
   Callgraph callgraph_;
 };

+
 class FunctionDeclarationFinder
     : public clang::ASTConsumer,
       public clang::RecursiveASTVisitor<FunctionDeclarationFinder> {
  public:
   explicit FunctionDeclarationFinder(clang::Diagnostic& d,
-                                     clang::SourceManager& sm)
+                                     clang::SourceManager& sm,
+                                     const std::vector<std::string>& args)
       : d_(d), sm_(sm) { }

   virtual void HandleTranslationUnit(clang::ASTContext &ctx) {
@@ -202,100 +245,807 @@
 }


-static bool IsHandleType(const clang::DeclarationName& handleDeclName,
-                         const clang::QualType& qtype) {
-  const clang::Type* canonical_type =
-      qtype.getTypePtr()->getCanonicalTypeUnqualified().getTypePtr();
-
-  if (const clang::TemplateSpecializationType* type =
-          canonical_type->getAs<clang::TemplateSpecializationType>()) {
-    if (clang::TemplateDecl* decl =
-            type->getTemplateName().getAsTemplateDecl()) {
-      if (decl->getTemplatedDecl()->getDeclName() == handleDeclName) {
-        return true;
-      }
-    }
-  } else if (const clang::RecordType* type =
-                 canonical_type->getAs<clang::RecordType>()) {
-    if (const clang::ClassTemplateSpecializationDecl* t =
- dyn_cast<clang::ClassTemplateSpecializationDecl>(type->getDecl())) {
-      if (t->getSpecializedTemplate()->getDeclName() == handleDeclName) {
-        return true;
+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 << "}";
   }

-  return false;
+  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);
 }


-class ExpressionClassifier :
-    public clang::RecursiveASTVisitor<ExpressionClassifier> {
+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:
-  ExpressionClassifier(clang::DeclarationName handleDeclName,
-                       clang::MangleContext* ctx,
-                       clang::CXXRecordDecl* objectDecl)
-      : handleDeclName_(handleDeclName),
-        ctx_(ctx),
-        objectDecl_(objectDecl) {
+  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) {
   }

-  bool IsBadExpression(clang::Expr* expr) {
-    has_derefs_ = has_gc_ = false;
-    TraverseStmt(expr);
-    return has_derefs_ && has_gc_;
+
+ // --------------------------------------------------------------------------
+  // 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();
   }

-  bool IsBadCallSite(clang::Expr* expr) {
-    if (isa<clang::CallExpr>(expr)) {
-      clang::CallExpr* call = cast<clang::CallExpr>(expr);
-
-      MarkGCSuspectAsArgument(call);
-      MarkHandleDereferenceAsArgument(call);
-
-      return derefs_.any() &&
-          ((gc_.count() > 1) || (gc_.any() && (gc_ ^ derefs_).any()));
+#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;
   }

-  virtual bool VisitExpr(clang::Expr* expr) {
-    has_derefs_ = has_derefs_ || IsRawPointerType(expr);
-    return !has_gc_ || !has_derefs_;
+  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);
+    }
   }

-  virtual bool VisitCallExpr(clang::CallExpr* expr) {
-    has_gc_ = has_gc_ || CanCauseGC(expr);
-    return !has_gc_ || !has_derefs_;
-  }
- private:
-  void MarkHandleDereferenceAsArgument(clang::CallExpr* call) {
-    derefs_.reset();
-
-    if (clang::CXXMemberCallExpr* memcall =
-            dyn_cast<clang::CXXMemberCallExpr>(call)) {
-      if (ManipulatesRawPointers(memcall->getImplicitObjectArgument())) {
-        derefs_.set(0);
+  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++) {
-      if (ManipulatesRawPointers(call->getArg(arg))) derefs_.set(arg + 1);
+      props->SetEffect(arg + 1, VisitExpr(call->getArg(arg), env));
     }
   }

-  void MarkGCSuspectAsArgument(clang::CallExpr* call) {
-    gc_.reset();
+
+  ExprEffect VisitCallExpr(clang::CallExpr* call,
+                           const Environment& env) {
+    CallProps props;

     clang::CXXMemberCallExpr* memcall =
         dyn_cast_or_null<clang::CXXMemberCallExpr>(call);
- if (memcall != NULL && CanCauseGC(memcall->getImplicitObjectArgument())) {
-      gc_.set(0);
+    if (memcall != NULL) {
+      clang::Expr* receiver = memcall->getImplicitObjectArgument();
+      props.SetEffect(0, VisitExpr(receiver, env));
     }

-    for (unsigned arg = 0; arg < call->getNumArgs(); arg++) {
-      if (CanCauseGC(call->getArg(arg))) gc_.set(arg + 1);
-    }
+    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) {
@@ -311,11 +1061,14 @@
     }
   }

-  bool IsRawPointerType(clang::Expr* expr) {
-    clang::QualType result = expr->getType();
-
+  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>(expr->getType().getTypePtr());
+        dyn_cast_or_null<clang::PointerType>(qtype.getTypePtrOrNull());
     if (type == NULL) return false;

     const clang::TagType* pointee =
@@ -326,146 +1079,154 @@
         dyn_cast_or_null<clang::CXXRecordDecl>(pointee->getDecl());
     if (record == NULL) return false;

-    return InV8Namespace(record) &&
-        record->hasDefinition() &&
-        ((record == objectDecl_) || record->isDerivedFrom(objectDecl_));
-  }
-
-  bool IsHandleDereference(clang::Expr* expr) {
-    if (expr == NULL) {
-      return false;
-    } else if (isa<clang::UnaryOperator>(expr)) {
-      clang::UnaryOperator* unop = cast<clang::UnaryOperator>(expr);
-      return unop->getOpcode() == clang::UO_Deref &&
-          IsHandleType(handleDeclName_, unop->getSubExpr()->getType());
-    } else if (isa<clang::CXXOperatorCallExpr>(expr)) {
- clang::CXXOperatorCallExpr* op = cast<clang::CXXOperatorCallExpr>(expr);
-      return (op->getOperator() == clang::OO_Star ||
-              op->getOperator() == clang::OO_Arrow) &&
-          IsHandleType(handleDeclName_, op->getArg(0)->getType());
-    } else {
-      return false;
-    }
-  }
-
-  bool CanCauseGC(clang::Expr* expr) {
-    if (expr == NULL) return false;
-
-    has_gc_ = false;
-    has_derefs_ = true;
-    TraverseStmt(expr);
-    return has_gc_;
+    if (!InV8Namespace(record)) return false;
+
+    if (!record->hasDefinition()) return false;
+
+    record = record->getDefinition();
+
+    return IsDerivedFrom(record, object_decl_) &&
+        !IsDerivedFrom(record, smi_decl_);
   }

***The diff for this file has been truncated for email.***
=======================================
--- /branches/bleeding_edge/tools/gcmole/gcmole.lua     Thu Apr  7 05:38:16 2011
+++ /branches/bleeding_edge/tools/gcmole/gcmole.lua     Mon May 16 02:06:16 2011
@@ -29,8 +29,44 @@
-- Usage: CLANG_BIN=clang-bin-dir lua tools/gcmole/gcmole.lua [arm|ia32| x64]

 local DIR = arg[0]:match("^(.+)/[^/]+$")
-
-local ARCHS = arg[1] and { arg[1] } or { 'ia32', 'arm', 'x64' }
+
+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"
@@ -43,33 +79,40 @@
-------------------------------------------------------------------------------
 -- Clang invocation

-local CLANG_BIN = os.getenv "CLANG_BIN"
+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, triple, arch_define)
-   return CLANG_BIN .. "/clang -cc1 -load " .. DIR .. "/libgcmole.so"
+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
-      .. " -triple " .. triple
+      .. (plugin_args or "")
+      .. " -triple " .. triple
       .. " -D" .. arch_define
-      .. " -DENABLE_VMSTATE_TRACKING"
-      .. " -DENABLE_LOGGING_AND_PROFILING"
+      .. " -DENABLE_VMSTATE_TRACKING"
+      .. " -DENABLE_LOGGING_AND_PROFILING"
       .. " -DENABLE_DEBUGGER_SUPPORT"
       .. " -Isrc"
 end

 function InvokeClangPluginForEachFile(filenames, cfg, func)
    local cmd_line = MakeClangCommandLine(cfg.plugin,
-                                        cfg.triple,
-                                        cfg.arch_define)
-
-   for _, filename in ipairs(filenames) do
+                                         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()
@@ -84,7 +127,7 @@
    local sconscript = f:read('*a')
    f:close()

-   local SOURCES = sconscript:match "SOURCES = {(.-)}";
+   local SOURCES = sconscript:match "SOURCES = {(.-)}";

    local sources = {}

@@ -93,13 +136,13 @@
       local files = {}
       for file in list:gmatch "[^%s]+" do table.insert(files, file) end
       sources[condition] = files
-   end
+   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
+   end

    return sources
 end
@@ -119,7 +162,7 @@
    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
+         for i = 1, #files do table.insert(list, files[i]) end
       end
    end
    return list
@@ -129,9 +172,9 @@

 local function FilesForArch(arch)
    return BuildFileList(sources, { os = 'linux',
-                                  arch = arch,
-                                  mode = 'debug',
-                                  simulator = ''})
+                                   arch = arch,
+                                   mode = 'debug',
+                                   simulator = ''})
 end

 local mtConfig = {}
@@ -149,29 +192,67 @@

 local ARCHITECTURES = {
    ia32 = config { triple = "i586-unknown-linux",
-                  arch_define = "V8_TARGET_ARCH_IA32" },
+                   arch_define = "V8_TARGET_ARCH_IA32" },
    arm = config { triple = "i586-unknown-linux",
-                 arch_define = "V8_TARGET_ARCH_ARM" },
+                  arch_define = "V8_TARGET_ARCH_ARM" },
    x64 = config { triple = "x86_64-unknown-linux",
-                 arch_define = "V8_TARGET_ARCH_X64" }
+                  arch_define = "V8_TARGET_ARCH_X64" }
 }

-------------------------------------------------------------------------------
--- GCSuspects Generation
-
-local gc = {}
-local funcs = {}
+-- 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
+
+   if not f then
       f = {}
       funcs[name] = f
-
-      if name:match "Collect.*Garbage" then gc[name] = true end
+
+      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

@@ -180,11 +261,11 @@

    for funcname in lines do
       if funcname:sub(1, 1) ~= '\t' then
-        resolve(funcname)
-        scope = funcname
+         resolve(funcname)
+         scope = funcname
       else
-        local name = funcname:sub(2)
-        resolve(name)[scope] = true
+         local name = funcname:sub(2)
+         resolve(name)[scope] = true
       end
    end
 end
@@ -192,60 +273,82 @@
 local function propagate ()
    log "** Propagating GC information"

-   local function mark(callers)
-      for caller, _ in pairs(callers) do
-        if not gc[caller] then
-           gc[caller] = true
-           mark(funcs[caller])
-        end
+   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(callers) end
+      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, _ in pairs(gc) do out:write (name, '\n') end
+ 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 function CheckCorrectnessForArch(arch)
    local files = FilesForArch(arch)
    local cfg = ARCHITECTURES[arch]

-   GenerateGCSuspects(arch, files, cfg)
+   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"
+         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 for %s", arch)
+   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" },
-                               SearchForErrors)
+                                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")

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to