Update gcmole to work with llvm 8 and the new Object design

After introducing the new pointer-containing Object class in V8 (see
https://docs.google.com/document/d/1_w49sakC1XM1OptjTurBDqO86NE16FH8LwbeUAtrbCo/edit),
gcmole stopped finding errorneous usage of raw pointers in functions that could
trigger GC. This CL modifies the heuristics of the tool to classify Object and
MaybeObject instances as raw pointers, thus giving back the missing warnings.

Updated the gcmole implementation to support modern llvm (tested with llvm 8.0)
for which additional support for MaterializeTemporaryExpr, ExprWithCleanups and
UnaryExprOrTypeTraitExpr was needed.

Basic tests are added to make it harder to introduce such errors without
noticing in the future.

This version gives a lot of false positives when ran on the whole project, see
https://docs.google.com/document/d/1K7eJ0f6m9QX6FZIjZnt_GFtUsjEOC_LpiAwZbcAA3f8/edit

R=jkummerow@chromium.org,mstarzinger@chromium.org

Bug: v8:8813
Change-Id: Ic0190a4bc2642eda8880d9f7b30b5145a76a7d89
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1494754
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#60099}
This commit is contained in:
Maya Lekova 2019-03-07 15:43:43 +01:00 committed by Commit Bot
parent 6e98fa925e
commit 45ae9e0ae9
3 changed files with 207 additions and 40 deletions

View File

@ -21,7 +21,7 @@ PREREQUISITES -----------------------------------------------------------------
1) Install Lua 5.1
2) Get LLVM 2.9 and Clang 2.9 sources and build them.
2) Get LLVM 8.0 and Clang 8.0 sources and build them.
Follow the instructions on http://clang.llvm.org/get_started.html.
@ -60,3 +60,27 @@ warning. Messages "Failed to resolve v8::internal::Object" are benign and
can be ignored.
If any errors were found driver exits with non-zero status.
TROUBLESHOOTING ------------------------
gcmole is tighly coupled with the AST structure that Clang produces. Therefore
when upgrading to a newer Clang version, it might start producing bogus output
or completely stop outputting warnings. In such occasion, one might start the
debugging process by checking weather a new AST node type is introduced which
is currently not supported by gcmole. Insert the following code at the end of
the FunctionAnalyzer::VisitExpr method to see the unsupported AST class(es)
and the source position which generates them:
if (expr) {
clang::Stmt::StmtClass stmtClass = expr->getStmtClass();
d_.Report(clang::FullSourceLoc(expr->getExprLoc(), sm_),
d_.getCustomDiagID(clang::DiagnosticsEngine::Remark, "%0")) << stmtClass;
}
For instance, gcmole currently doesn't support AtomicExprClass statements
introduced for atomic operations.
A convenient way to observe the AST generated by Clang is to pass the following
flags when invoking clang++
-Xclang -ast-dump -fsyntax-only

View File

@ -0,0 +1,60 @@
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/handles-inl.h"
#include "src/handles.h"
#include "src/isolate.h"
#include "src/objects/maybe-object.h"
#include "src/objects/object-macros.h"
namespace v8 {
namespace internal {
Handle<Object> CauseGC(Handle<Object> obj, Isolate* isolate) {
isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
return obj;
}
void TwoArgumentsFunction(Object a, Object b) {
a->Print();
b->Print();
}
void TestTwoArguments(Isolate* isolate) {
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
Handle<JSObject> obj2 = isolate->factory()->NewJSObjectWithNullProto();
TwoArgumentsFunction(*CauseGC(obj1, isolate), *CauseGC(obj2, isolate));
}
void TwoSizeTArgumentsFunction(size_t a, size_t b) {
USE(a);
USE(b);
}
void TestTwoSizeTArguments(Isolate* isolate) {
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
Handle<JSObject> obj2 = isolate->factory()->NewJSObjectWithNullProto();
TwoSizeTArgumentsFunction(sizeof(*CauseGC(obj1, isolate)),
sizeof(*CauseGC(obj2, isolate)));
}
class SomeObject : public Object {
public:
void Method(Object a) { a->Print(); }
DECL_CAST(SomeObject)
OBJECT_CONSTRUCTORS(SomeObject, Object);
};
void TestMethodCall(Isolate* isolate) {
SomeObject obj;
Handle<SomeObject> so = handle(obj, isolate);
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
so->Method(*CauseGC(obj1, isolate));
}
} // namespace internal
} // namespace v8

View File

@ -458,7 +458,9 @@ class CallProps {
CallProps() : env_(NULL) { }
void SetEffect(int arg, ExprEffect in) {
if (in.hasGC()) gc_.set(arg);
if (in.hasGC()) {
gc_.set(arg);
}
if (in.hasRawDef()) raw_def_.set(arg);
if (in.hasRawUse()) raw_use_.set(arg);
if (in.env() != NULL) {
@ -472,17 +474,24 @@ class CallProps {
ExprEffect ComputeCumulativeEffect(bool result_is_raw) {
ExprEffect out = ExprEffect::NoneWithEnv(env_);
if (gc_.any()) out.setGC();
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;
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());
if (!raw.any()) {
return true;
}
bool result = gc_.count() == 1 && !((raw ^ gc_).any());
return result;
}
private:
@ -539,18 +548,19 @@ class FunctionAnalyzer {
FunctionAnalyzer(clang::MangleContext* ctx,
clang::DeclarationName handle_decl_name,
clang::CXXRecordDecl* object_decl,
clang::CXXRecordDecl* maybe_object_decl,
clang::CXXRecordDecl* smi_decl, clang::DiagnosticsEngine& d,
clang::SourceManager& sm, bool dead_vars_analysis)
: ctx_(ctx),
handle_decl_name_(handle_decl_name),
object_decl_(object_decl),
maybe_object_decl_(maybe_object_decl),
smi_decl_(smi_decl),
d_(d),
sm_(sm),
block_(NULL),
dead_vars_analysis_(dead_vars_analysis) {}
// --------------------------------------------------------------------------
// Expressions
// --------------------------------------------------------------------------
@ -574,6 +584,7 @@ class FunctionAnalyzer {
VISIT(CharacterLiteral);
VISIT(ChooseExpr);
VISIT(CompoundLiteralExpr);
VISIT(ConstantExpr);
VISIT(CXXBindTemporaryExpr);
VISIT(CXXBoolLiteralExpr);
VISIT(CXXConstructExpr);
@ -598,9 +609,11 @@ class FunctionAnalyzer {
VISIT(FloatingLiteral);
VISIT(GNUNullExpr);
VISIT(ImaginaryLiteral);
VISIT(ImplicitCastExpr);
VISIT(ImplicitValueInitExpr);
VISIT(InitListExpr);
VISIT(IntegerLiteral);
VISIT(MaterializeTemporaryExpr);
VISIT(MemberExpr);
VISIT(OffsetOfExpr);
VISIT(OpaqueValueExpr);
@ -616,6 +629,7 @@ class FunctionAnalyzer {
VISIT(SubstNonTypeTemplateParmPackExpr);
VISIT(TypeTraitExpr);
VISIT(UnaryOperator);
VISIT(UnaryExprOrTypeTraitExpr);
VISIT(VAArgExpr);
#undef VISIT
@ -685,6 +699,7 @@ class FunctionAnalyzer {
llvm::cast<clang::DeclRefExpr>(expr)->getDecl()->getNameAsString();
return true;
}
return false;
}
@ -718,6 +733,10 @@ class FunctionAnalyzer {
return VisitExpr(expr->getSubExpr(), env);
}
DECL_VISIT_EXPR(MaterializeTemporaryExpr) {
return VisitExpr(expr->GetTemporaryExpr(), env);
}
DECL_VISIT_EXPR(CXXConstructExpr) {
return VisitArguments<>(expr, env);
}
@ -740,6 +759,12 @@ class FunctionAnalyzer {
return VisitExpr(expr->getSubExpr(), env);
}
DECL_VISIT_EXPR(ImplicitCastExpr) {
return VisitExpr(expr->getSubExpr(), env);
}
DECL_VISIT_EXPR(ConstantExpr) { return VisitExpr(expr->getSubExpr(), env); }
DECL_VISIT_EXPR(InitListExpr) {
return Seq(expr, expr->getNumInits(), expr->getInits(), env);
}
@ -776,6 +801,14 @@ class FunctionAnalyzer {
return VisitExpr(expr->getSubExpr(), env);
}
DECL_VISIT_EXPR(UnaryExprOrTypeTraitExpr) {
if (expr->isArgumentType()) {
return ExprEffect::None();
}
return VisitExpr(expr->getArgumentExpr(), env);
}
DECL_VISIT_EXPR(CastExpr) {
return VisitExpr(expr->getSubExpr(), env);
}
@ -796,7 +829,8 @@ class FunctionAnalyzer {
if (!props.IsSafe()) ReportUnsafe(parent, BAD_EXPR_MSG);
return props.ComputeCumulativeEffect(IsRawPointerType(parent->getType()));
return props.ComputeCumulativeEffect(
RepresentsRawPointerType(parent->getType()));
}
ExprEffect Seq(clang::Stmt* parent,
@ -816,7 +850,7 @@ class FunctionAnalyzer {
const clang::QualType& var_type,
const std::string& var_name,
const Environment& env) {
if (IsRawPointerType(var_type)) {
if (RepresentsRawPointerType(var_type)) {
if (!env.IsAlive(var_name) && dead_vars_analysis_) {
ReportUnsafe(parent, DEAD_VAR_MSG);
}
@ -840,7 +874,8 @@ class FunctionAnalyzer {
CallProps props;
VisitArguments<>(call, &props, env);
if (!props.IsSafe()) ReportUnsafe(call, BAD_EXPR_MSG);
return props.ComputeCumulativeEffect(IsRawPointerType(call->getType()));
return props.ComputeCumulativeEffect(
RepresentsRawPointerType(call->getType()));
}
template<typename ExprType>
@ -868,8 +903,8 @@ class FunctionAnalyzer {
if (!props.IsSafe()) ReportUnsafe(call, BAD_EXPR_MSG);
ExprEffect out =
props.ComputeCumulativeEffect(IsRawPointerType(call->getType()));
ExprEffect out = props.ComputeCumulativeEffect(
RepresentsRawPointerType(call->getType()));
clang::FunctionDecl* callee = call->getDirectCallee();
if ((callee != NULL) && KnownToCauseGC(ctx_, callee)) {
@ -1104,39 +1139,73 @@ class FunctionAnalyzer {
}
}
bool IsDerivedFrom(clang::CXXRecordDecl* record,
clang::CXXRecordDecl* base) {
bool IsDerivedFrom(const clang::CXXRecordDecl* record,
const clang::CXXRecordDecl* base) {
return (record == base) || record->isDerivedFrom(base);
}
bool IsRawPointerType(clang::QualType qtype) {
const clang::PointerType* type =
const clang::CXXRecordDecl* GetDefinitionOrNull(
const clang::CXXRecordDecl* record) {
if (record == NULL) {
return NULL;
}
if (!InV8Namespace(record)) return NULL;
if (!record->hasDefinition()) {
return NULL;
}
return record->getDefinition();
}
bool IsRawPointerType(const clang::PointerType* type, clang::QualType qtype) {
const clang::CXXRecordDecl* record = type->getPointeeCXXRecordDecl();
const clang::CXXRecordDecl* definition = GetDefinitionOrNull(record);
if (!definition) {
return false;
}
return !IsDerivedFrom(record, smi_decl_);
}
bool IsInternalPointerType(clang::QualType qtype) {
if (qtype.isNull()) {
return false;
}
if (qtype->isNullPtrType()) {
return true;
}
const clang::CXXRecordDecl* record = qtype->getAsCXXRecordDecl();
const clang::CXXRecordDecl* definition = GetDefinitionOrNull(record);
if (!definition) {
return false;
}
return IsDerivedFrom(record, object_decl_) ||
IsDerivedFrom(record, maybe_object_decl_);
}
// Returns weather the given type is a raw pointer or a wrapper around
// such. For V8 that means Object and MaybeObject instances.
bool RepresentsRawPointerType(clang::QualType qtype) {
const clang::PointerType* pointer_type =
llvm::dyn_cast_or_null<clang::PointerType>(qtype.getTypePtrOrNull());
if (type == NULL) return false;
const clang::TagType* pointee =
ToTagType(type->getPointeeType().getTypePtr());
if (pointee == NULL) return false;
clang::CXXRecordDecl* record =
llvm::dyn_cast_or_null<clang::CXXRecordDecl>(pointee->getDecl());
if (record == NULL) return false;
if (!InV8Namespace(record)) return false;
if (!record->hasDefinition()) return false;
record = record->getDefinition();
return IsDerivedFrom(record, object_decl_) &&
!IsDerivedFrom(record, smi_decl_);
if (pointer_type != NULL) {
return IsRawPointerType(pointer_type, qtype);
} else {
return IsInternalPointerType(qtype);
}
}
Environment VisitDecl(clang::Decl* decl, const Environment& env) {
if (clang::VarDecl* var = llvm::dyn_cast<clang::VarDecl>(decl)) {
Environment out = var->hasInit() ? VisitStmt(var->getInit(), env) : env;
if (IsRawPointerType(var->getType())) {
if (RepresentsRawPointerType(var->getType())) {
out = out.Define(var->getNameAsString());
}
@ -1201,6 +1270,7 @@ class FunctionAnalyzer {
clang::MangleContext* ctx_;
clang::DeclarationName handle_decl_name_;
clang::CXXRecordDecl* object_decl_;
clang::CXXRecordDecl* maybe_object_decl_;
clang::CXXRecordDecl* smi_decl_;
clang::DiagnosticsEngine& d_;
@ -1231,23 +1301,35 @@ class ProblemsFinder : public clang::ASTConsumer,
r.ResolveNamespace("v8").ResolveNamespace("internal").
Resolve<clang::CXXRecordDecl>("Object");
clang::CXXRecordDecl* maybe_object_decl =
r.ResolveNamespace("v8")
.ResolveNamespace("internal")
.Resolve<clang::CXXRecordDecl>("MaybeObject");
clang::CXXRecordDecl* smi_decl =
r.ResolveNamespace("v8").ResolveNamespace("internal").
Resolve<clang::CXXRecordDecl>("Smi");
if (object_decl != NULL) object_decl = object_decl->getDefinition();
if (maybe_object_decl != NULL)
maybe_object_decl = maybe_object_decl->getDefinition();
if (smi_decl != NULL) smi_decl = smi_decl->getDefinition();
if (object_decl != NULL && smi_decl != NULL) {
if (object_decl != NULL && smi_decl != NULL && maybe_object_decl != NULL) {
function_analyzer_ = new FunctionAnalyzer(
clang::ItaniumMangleContext::create(ctx, d_), r.ResolveName("Handle"),
object_decl, smi_decl, d_, sm_, dead_vars_analysis_);
object_decl, maybe_object_decl, smi_decl, d_, sm_,
dead_vars_analysis_);
TraverseDecl(ctx.getTranslationUnitDecl());
} else {
if (object_decl == NULL) {
llvm::errs() << "Failed to resolve v8::internal::Object\n";
}
if (maybe_object_decl == NULL) {
llvm::errs() << "Failed to resolve v8::internal::MaybeObject\n";
}
if (smi_decl == NULL) {
llvm::errs() << "Failed to resolve v8::internal::Smi\n";
}
@ -1271,9 +1353,10 @@ class ProblemsFinder : public clang::ASTConsumer,
template<typename ConsumerType>
class Action : public clang::PluginASTAction {
protected:
clang::ASTConsumer *CreateASTConsumer(clang::CompilerInstance &CI,
llvm::StringRef InFile) {
return new ConsumerType(CI.getDiagnostics(), CI.getSourceManager(), args_);
virtual std::unique_ptr<clang::ASTConsumer> CreateASTConsumer(
clang::CompilerInstance& CI, llvm::StringRef InFile) {
return std::unique_ptr<clang::ASTConsumer>(
new ConsumerType(CI.getDiagnostics(), CI.getSourceManager(), args_));
}
bool ParseArgs(const clang::CompilerInstance &CI,