Commit 850b1064 authored by vegorov@chromium.org's avatar vegorov@chromium.org

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@7895 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent e90632f4
......@@ -1757,7 +1757,7 @@ MaybeObject* MacroAssembler::TryTailCallStub(CodeStub* stub, Condition cond) {
{ 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;
}
......
......@@ -3528,7 +3528,8 @@ MaybeObject* ExternalArrayStubCompiler::CompileKeyedLoadStub(
__ 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
......
......@@ -1701,14 +1701,14 @@ void Genesis::InstallBuiltinFunctionIds() {
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 @@ void Genesis::InstallJSFunctionResultCaches() {
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);
......
......@@ -527,12 +527,12 @@ static Handle<Object> UnwrapJSValue(Handle<JSValue> jsValue) {
// 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 @@ class FunctionInfoWrapper : public JSArrayBasedStruct<FunctionInfoWrapper> {
}
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 @@ class SharedInfoWrapper : public JSArrayBasedStruct<SharedInfoWrapper> {
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);
......
......@@ -2873,8 +2873,9 @@ MaybeObject* JSObject::DeleteElement(uint32_t index, DeleteMode mode) {
// 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 @@ MaybeObject* JSObject::GetElementWithCallback(Object* receiver,
// 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 @@ MaybeObject* JSObject::SetElementWithCallback(Object* structure,
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 @@ MaybeObject* JSObject::SetElementWithoutInterceptor(uint32_t index,
// 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",
......
......@@ -7928,12 +7928,13 @@ static ObjectPair LoadContextSlotHelper(Arguments args,
// 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 @@ static ObjectPair LoadContextSlotHelper(Arguments args,
} 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) {
......
......@@ -166,7 +166,10 @@ function URIDecodeOctets(octets, result, index) {
// 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 @@ function Encode(uri, unescape) {
// 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);
......
-- 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
......@@ -69,6 +69,47 @@ static bool InV8Namespace(const clang::NamedDecl* decl) {
}
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 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
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 KnownToCauseGC(clang::MangleContext* ctx,
}
static bool IsHandleType(const clang::DeclarationName& handleDeclName,
const clang::QualType& qtype) {
const clang::Type* canonical_type =
qtype.getTypePtr()->getCanonicalTypeUnqualified().getTypePtr();
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;
if (const clang::TemplateSpecializationType* type =
canonical_type->getAs<clang::TemplateSpecializationType>()) {
if (clang::TemplateDecl* decl =
type->getTemplateName().getAsTemplateDecl()) {
if (decl->getTemplatedDecl()->getDeclName() == handleDeclName) {
return true;
}
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;
}
} 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;
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);
}
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);
}
class ExpressionClassifier :
public clang::RecursiveASTVisitor<ExpressionClassifier> {
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);
#define DECL_VISIT_EXPR(type) \
ExprEffect Visit##type (clang::type* expr, const Environment& env)
MarkGCSuspectAsArgument(call);
MarkHandleDereferenceAsArgument(call);
#define IGNORE_EXPR(type) \
ExprEffect Visit##type (clang::type* expr, const Environment& env) { \
return ExprEffect::None(); \
}
return derefs_.any() &&
((gc_.count() > 1) || (gc_.any() && (gc_ ^ derefs_).any()));
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_;
DECL_VISIT_EXPR(CXXBindTemporaryExpr) {
return VisitExpr(expr->getSubExpr(), env);
}
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(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 @@ class ExpressionClassifier :
}
}
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 @@ class ExpressionClassifier :
dyn_cast_or_null<clang::CXXRecordDecl>(pointee->getDecl());
if (record == NULL) return false;
return InV8Namespace(record) &&
record->hasDefinition() &&
((record == objectDecl_) || record->isDerivedFrom(objectDecl_));
}
if (!InV8Namespace(record)) return false;
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;
}
}
if (!record->hasDefinition()) return false;
bool CanCauseGC(clang::Expr* expr) {
if (expr == NULL) return false;
record = record->getDefinition();
has_gc_ = false;
has_derefs_ = true;
TraverseStmt(expr);
return has_gc_;
return IsDerivedFrom(record, object_decl_) &&
!IsDerivedFrom(record, smi_decl_);
}
bool ManipulatesRawPointers(clang::Expr* expr) {
if (expr == NULL) return false;
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());
}
has_gc_ = true;
has_derefs_ = false;
TraverseStmt(expr);
return has_derefs_;
return out;
}
// TODO: handle other declarations?
return env;
}
bool CanCauseGC(const clang::CallExpr* call) {
const clang::FunctionDecl* fn = call->getDirectCallee();
return (fn != NULL) && KnownToCauseGC(ctx_, fn);
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;
}
// For generic expression classification.
bool has_derefs_;
bool has_gc_;
// For callsite classification.
static const int kMaxNumberOfArguments = 64;
std::bitset<kMaxNumberOfArguments> derefs_;
std::bitset<kMaxNumberOfArguments> gc_;
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());
}
}
clang::DeclarationName handleDeclName_;
clang::MangleContext* ctx_;
clang::CXXRecordDecl* objectDecl_;
};
const std::string BAD_EXPRESSION_MSG("Possible problem with evaluation order.");
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();
}
}
class ExpressionsFinder : public clang::ASTConsumer,
public clang::RecursiveASTVisitor<ExpressionsFinder> {
public:
explicit ExpressionsFinder(clang::Diagnostic& d, clang::SourceManager& sm)
: d_(d), sm_(sm) { }
Block* EnterBlock(Block* block) {
Block* parent = block_;
block_ = block;
return parent;
}
struct Resolver {
explicit Resolver(clang::ASTContext& ctx)
: ctx_(ctx), decl_ctx_(ctx.getTranslationUnitDecl()) {
}
void LeaveBlock(Block* block) {
block_ = block;
}
Resolver(clang::ASTContext& ctx, clang::DeclContext* decl_ctx)
: ctx_(ctx), decl_ctx_(decl_ctx) {
}
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::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));
}
clang::MangleContext* ctx_;
clang::DeclarationName handle_decl_name_;
clang::CXXRecordDecl* object_decl_;
clang::CXXRecordDecl* smi_decl_;
template<typename T>
T* Resolve(const char* n) {
if (decl_ctx_ == NULL) return NULL;
clang::Diagnostic& d_;
clang::SourceManager& sm_;
clang::DeclContext::lookup_result result =
decl_ctx_->lookup(ResolveName(n));
Block* block_;
bool dead_vars_analysis_;
};
for (clang::DeclContext::lookup_iterator i = result.first,
e = result.second;
i != e;
i++) {
if (isa<T>(*i)) return cast<T>(*i);
}
return NULL;
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;
}
}
private:
clang::ASTContext& ctx_;
clang::DeclContext* decl_ctx_;
};
}
virtual void HandleTranslationUnit(clang::ASTContext &ctx) {
Resolver r(ctx);
clang::CXXRecordDecl* objectDecl =
clang::CXXRecordDecl* object_decl =
r.ResolveNamespace("v8").ResolveNamespace("internal").
Resolve<clang::CXXRecordDecl>("Object");
if (objectDecl != NULL) {
expression_classifier_ =
new ExpressionClassifier(r.ResolveName("Handle"),
clang::createItaniumMangleContext(ctx, d_),
objectDecl);
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 {
std::cerr << "Failed to resolve v8::internal::Object" << std::endl;
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 VisitExpr(clang::Expr* expr) {
if ( expression_classifier_->IsBadCallSite(expr) ) {
d_.Report(clang::FullSourceLoc(expr->getExprLoc(), sm_),
d_.getCustomDiagID(clang::Diagnostic::Warning,
BAD_EXPRESSION_MSG));
}
virtual bool VisitFunctionDecl(clang::FunctionDecl* decl) {
function_analyzer_->AnalyzeFunction(decl);
return true;
}
private:
clang::Diagnostic& d_;
clang::SourceManager& sm_;
bool dead_vars_analysis_;
ExpressionClassifier* expression_classifier_;
FunctionAnalyzer* function_analyzer_;
};
......@@ -474,22 +1235,27 @@ class Action : public clang::PluginASTAction {
protected:
clang::ASTConsumer *CreateASTConsumer(clang::CompilerInstance &CI,
llvm::StringRef InFile) {
return new ConsumerType(CI.getDiagnostics(), CI.getSourceManager());
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) { }
void PrintHelp(llvm::raw_ostream& ros) {
}
private:
std::vector<std::string> args_;
};
}
static clang::FrontendPluginRegistry::Add<Action<ExpressionsFinder> >
FindProblems("find-problems", "Find possible problems with evaluations order.");
static clang::FrontendPluginRegistry::Add<Action<ProblemsFinder> >
FindProblems("find-problems", "Find GC-unsafe places.");
static clang::FrontendPluginRegistry::Add<Action<FunctionDeclarationFinder> >
static clang::FrontendPluginRegistry::Add<
Action<FunctionDeclarationFinder> >
DumpCallees("dump-callees", "Dump callees for each function.");
......@@ -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 @@ end
-------------------------------------------------------------------------------
-- 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
end
local function MakeClangCommandLine(plugin, triple, arch_define)
return CLANG_BIN .. "/clang -cc1 -load " .. DIR .. "/libgcmole.so"
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)
cfg.plugin_args,
cfg.triple,
cfg.arch_define)
for _, filename in ipairs(filenames) do
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 function ParseSConscript()
local sconscript = f:read('*a')
f:close()
local SOURCES = sconscript:match "SOURCES = {(.-)}";
local SOURCES = sconscript:match "SOURCES = {(.-)}";
local sources = {}
......@@ -93,13 +136,13 @@ local function ParseSConscript()
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 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
for i = 1, #files do table.insert(list, files[i]) end
end
end
return list
......@@ -129,9 +172,9 @@ local sources = ParseSConscript()
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 @@ end
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 @@ local function parse (filename, lines)
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 @@ end
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")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment