Commit 37a4937b authored by Maya Lekova's avatar Maya Lekova Committed by Commit Bot

[gcmole] Fix traversing virtual methods

Since this produces a few false positives, also implemented a whitelist
mechanism to not report them.

Also, add a couple of tests and implemented automated testing against
test-expectations file.

Bug: v8:9321
Change-Id: I2915a29fe1891e8bbc51118bbd95ae072c8de023
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1773243
Commit-Queue: Maya Lekova <mslekova@chromium.org>
Reviewed-by: 's avatarMichael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#63456}
parent f6057ff1
...@@ -9,10 +9,13 @@ group("v8_run_gcmole") { ...@@ -9,10 +9,13 @@ group("v8_run_gcmole") {
data = [ data = [
"gccause.lua", "gccause.lua",
"GCMOLE.gn",
"gcmole.lua", "gcmole.lua",
"gcmole-tools/", "gcmole-tools/",
"parallel.py", "parallel.py",
"run-gcmole.py", "run-gcmole.py",
"suspects.whitelist",
"test-expectations.txt",
# The following contains all relevant source and build files. # The following contains all relevant source and build files.
"../debug_helper/debug-helper.h", "../debug_helper/debug-helper.h",
......
action("gcmole") {
sources = [
### gcmole(all) ###
"tools/gcmole/gcmole-test.cc",
]
}
...@@ -71,6 +71,12 @@ can be ignored. ...@@ -71,6 +71,12 @@ can be ignored.
If any errors were found driver exits with non-zero status. If any errors were found driver exits with non-zero status.
TESTING -----------------------------------------------------------------------
Tests are automatically run by the main lua runner. Expectations are in
test-expectations.txt and need to be updated whenever the sources of the tests
in gcmole-test.cc are modified (line numbers also count).
PACKAGING --------------------------------------------------------------------- PACKAGING ---------------------------------------------------------------------
gcmole is deployed on V8's buildbot infrastructure to run it as part of the gcmole is deployed on V8's buildbot infrastructure to run it as part of the
......
...@@ -5,26 +5,43 @@ ...@@ -5,26 +5,43 @@
#include "src/execution/isolate.h" #include "src/execution/isolate.h"
#include "src/handles/handles-inl.h" #include "src/handles/handles-inl.h"
#include "src/handles/handles.h" #include "src/handles/handles.h"
#include "src/objects/foreign-inl.h"
#include "src/objects/managed.h"
#include "src/objects/maybe-object.h" #include "src/objects/maybe-object.h"
#include "src/objects/object-macros.h" #include "src/objects/object-macros.h"
namespace v8 { namespace v8 {
namespace internal { namespace internal {
// ------- Test simple argument evaluation order problems ---------
Handle<Object> CauseGC(Handle<Object> obj, Isolate* isolate) { Handle<Object> CauseGC(Handle<Object> obj, Isolate* isolate) {
isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting); isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
return obj; return obj;
} }
Object CauseGCRaw(Object obj, Isolate* isolate) {
isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
return obj;
}
Managed<Smi> CauseGCManaged(int i, Isolate* isolate) {
isolate->heap()->CollectGarbage(OLD_SPACE, GarbageCollectionReason::kTesting);
return Managed<Smi>::cast(Smi::FromInt(i));
}
void TwoArgumentsFunction(Object a, Object b) { void TwoArgumentsFunction(Object a, Object b) {
a->Print(); a.Print();
b->Print(); b.Print();
} }
void TestTwoArguments(Isolate* isolate) { void TestTwoArguments(Isolate* isolate) {
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto(); Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
Handle<JSObject> obj2 = isolate->factory()->NewJSObjectWithNullProto(); Handle<JSObject> obj2 = isolate->factory()->NewJSObjectWithNullProto();
// Should cause warning.
TwoArgumentsFunction(*CauseGC(obj1, isolate), *CauseGC(obj2, isolate)); TwoArgumentsFunction(*CauseGC(obj1, isolate), *CauseGC(obj2, isolate));
} }
...@@ -36,13 +53,16 @@ void TwoSizeTArgumentsFunction(size_t a, size_t b) { ...@@ -36,13 +53,16 @@ void TwoSizeTArgumentsFunction(size_t a, size_t b) {
void TestTwoSizeTArguments(Isolate* isolate) { void TestTwoSizeTArguments(Isolate* isolate) {
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto(); Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
Handle<JSObject> obj2 = isolate->factory()->NewJSObjectWithNullProto(); Handle<JSObject> obj2 = isolate->factory()->NewJSObjectWithNullProto();
// Should cause warning.
TwoSizeTArgumentsFunction(sizeof(*CauseGC(obj1, isolate)), TwoSizeTArgumentsFunction(sizeof(*CauseGC(obj1, isolate)),
sizeof(*CauseGC(obj2, isolate))); sizeof(*CauseGC(obj2, isolate)));
} }
// --------- Test problems with method arguments ----------
class SomeObject : public Object { class SomeObject : public Object {
public: public:
void Method(Object a) { a->Print(); } void Method(Object a) { a.Print(); }
SomeObject& operator=(const Object& b) { SomeObject& operator=(const Object& b) {
this->Print(); this->Print();
...@@ -58,14 +78,57 @@ void TestMethodCall(Isolate* isolate) { ...@@ -58,14 +78,57 @@ void TestMethodCall(Isolate* isolate) {
SomeObject obj; SomeObject obj;
Handle<SomeObject> so = handle(obj, isolate); Handle<SomeObject> so = handle(obj, isolate);
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto(); Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
// Should cause warning.
so->Method(*CauseGC(obj1, isolate)); so->Method(*CauseGC(obj1, isolate));
// Should cause warning.
so->Method(CauseGCRaw(*obj1, isolate));
} }
void TestOperatorCall(Isolate* isolate) { void TestOperatorCall(Isolate* isolate) {
SomeObject obj; SomeObject obj;
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto(); Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
// Should not cause warning.
obj = *CauseGC(obj1, isolate); obj = *CauseGC(obj1, isolate);
} }
// --------- Test for templated sub-classes of Object ----------
void TestFollowingTemplates(Isolate* isolate) {
// Should cause warning.
CauseGCManaged(42, isolate);
}
// --------- Test for correctly resolving virtual methods ----------
class BaseObject {
public:
virtual Handle<Object> VirtualCauseGC(Handle<Object> obj, Isolate* isolate) {
return obj;
}
};
class DerivedObject : public BaseObject {
public:
Handle<Object> VirtualCauseGC(Handle<Object> obj, Isolate* isolate) override {
isolate->heap()->CollectGarbage(OLD_SPACE,
GarbageCollectionReason::kTesting);
return obj;
}
};
void TestFollowingVirtualFunctions(Isolate* isolate) {
DerivedObject derived;
BaseObject* base = &derived;
Handle<JSObject> obj1 = isolate->factory()->NewJSObjectWithNullProto();
SomeObject so;
Handle<SomeObject> so_handle = handle(so, isolate);
// Should cause warning.
so_handle->Method(*derived.VirtualCauseGC(obj1, isolate));
// Should cause warning.
so_handle->Method(*base->VirtualCauseGC(obj1, isolate));
}
} // namespace internal } // namespace internal
} // namespace v8 } // namespace v8
...@@ -47,6 +47,7 @@ namespace { ...@@ -47,6 +47,7 @@ namespace {
typedef std::string MangledName; typedef std::string MangledName;
typedef std::set<MangledName> CalleesSet; typedef std::set<MangledName> CalleesSet;
typedef std::map<MangledName, MangledName> CalleesMap;
static bool GetMangledName(clang::MangleContext* ctx, static bool GetMangledName(clang::MangleContext* ctx,
const clang::NamedDecl* decl, const clang::NamedDecl* decl,
...@@ -138,14 +139,16 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> { ...@@ -138,14 +139,16 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
virtual bool VisitDeclRefExpr(clang::DeclRefExpr* expr) { virtual bool VisitDeclRefExpr(clang::DeclRefExpr* expr) {
// If function mentions EXTERNAL VMState add artificial garbage collection // If function mentions EXTERNAL VMState add artificial garbage collection
// mark. // mark.
if (IsExternalVMState(expr->getDecl())) AddCallee("CollectGarbage"); if (IsExternalVMState(expr->getDecl()))
AddCallee("CollectGarbage", "CollectGarbage");
return true; return true;
} }
void AnalyzeFunction(const clang::FunctionDecl* f) { void AnalyzeFunction(const clang::FunctionDecl* f) {
MangledName name; MangledName name;
if (InV8Namespace(f) && GetMangledName(ctx_, f, &name)) { if (InV8Namespace(f) && GetMangledName(ctx_, f, &name)) {
AddCallee(name); const std::string& function = f->getNameAsString();
AddCallee(name, function);
const clang::FunctionDecl* body = NULL; const clang::FunctionDecl* body = NULL;
if (f->hasBody(body) && !Analyzed(name)) { if (f->hasBody(body) && !Analyzed(name)) {
...@@ -176,21 +179,22 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> { ...@@ -176,21 +179,22 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
scopes_.pop(); scopes_.pop();
} }
void AddCallee(const MangledName& name) { void AddCallee(const MangledName& name, const MangledName& function) {
if (!scopes_.empty()) scopes_.top()->insert(name); if (!scopes_.empty()) scopes_.top()->insert(name);
mangled_to_function_[name] = function;
} }
void PrintCallGraph() { void PrintCallGraph() {
for (Callgraph::const_iterator i = callgraph_.begin(), e = callgraph_.end(); for (Callgraph::const_iterator i = callgraph_.begin(), e = callgraph_.end();
i != e; i != e;
++i) { ++i) {
std::cout << i->first << "\n"; std::cout << i->first << "," << mangled_to_function_[i->first] << "\n";
CalleesSet* callees = i->second; CalleesSet* callees = i->second;
for (CalleesSet::const_iterator j = callees->begin(), e = callees->end(); for (CalleesSet::const_iterator j = callees->begin(), e = callees->end();
j != e; j != e;
++j) { ++j) {
std::cout << "\t" << *j << "\n"; std::cout << "\t" << *j << "," << mangled_to_function_[*j] << "\n";
} }
} }
} }
...@@ -200,6 +204,7 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> { ...@@ -200,6 +204,7 @@ class CalleesPrinter : public clang::RecursiveASTVisitor<CalleesPrinter> {
std::stack<CalleesSet* > scopes_; std::stack<CalleesSet* > scopes_;
Callgraph callgraph_; Callgraph callgraph_;
CalleesMap mangled_to_function_;
}; };
...@@ -234,23 +239,40 @@ class FunctionDeclarationFinder ...@@ -234,23 +239,40 @@ class FunctionDeclarationFinder
CalleesPrinter* callees_printer_; CalleesPrinter* callees_printer_;
}; };
static bool gc_suspects_loaded = false;
static bool loaded = false;
static CalleesSet gc_suspects; static CalleesSet gc_suspects;
static CalleesSet gc_functions;
static bool whitelist_loaded = false;
static CalleesSet suspects_whitelist;
static void LoadGCSuspects() { static void LoadGCSuspects() {
if (loaded) return; if (gc_suspects_loaded) return;
std::ifstream fin("gcsuspects"); std::ifstream fin("gcsuspects");
std::string s; std::string mangled, function;
while (fin >> s) gc_suspects.insert(s); while (!fin.eof()) {
std::getline(fin, mangled, ',');
gc_suspects.insert(mangled);
std::getline(fin, function);
gc_functions.insert(function);
}
loaded = true; gc_suspects_loaded = true;
} }
static void LoadSuspectsWhitelist() {
if (whitelist_loaded) return;
std::ifstream fin("tools/gcmole/suspects.whitelist");
std::string s;
while (fin >> s) suspects_whitelist.insert(s);
whitelist_loaded = true;
}
// Looks for exact match of the mangled name
static bool KnownToCauseGC(clang::MangleContext* ctx, static bool KnownToCauseGC(clang::MangleContext* ctx,
const clang::FunctionDecl* decl) { const clang::FunctionDecl* decl) {
LoadGCSuspects(); LoadGCSuspects();
...@@ -265,6 +287,25 @@ static bool KnownToCauseGC(clang::MangleContext* ctx, ...@@ -265,6 +287,25 @@ static bool KnownToCauseGC(clang::MangleContext* ctx,
return false; return false;
} }
// Looks for partial match of only the function name
static bool SuspectedToCauseGC(clang::MangleContext* ctx,
const clang::FunctionDecl* decl) {
LoadGCSuspects();
if (!InV8Namespace(decl)) return false;
LoadSuspectsWhitelist();
if (suspects_whitelist.find(decl->getNameAsString()) !=
suspects_whitelist.end()) {
return false;
}
if (gc_functions.find(decl->getNameAsString()) != gc_functions.end()) {
return true;
}
return false;
}
static const int kNoEffect = 0; static const int kNoEffect = 0;
static const int kCausesGC = 1; static const int kCausesGC = 1;
...@@ -910,10 +951,32 @@ class FunctionAnalyzer { ...@@ -910,10 +951,32 @@ class FunctionAnalyzer {
RepresentsRawPointerType(call->getType())); RepresentsRawPointerType(call->getType()));
clang::FunctionDecl* callee = call->getDirectCallee(); clang::FunctionDecl* callee = call->getDirectCallee();
if ((callee != NULL) && KnownToCauseGC(ctx_, callee)) { if (callee != NULL) {
if (KnownToCauseGC(ctx_, callee)) {
out.setGC(); out.setGC();
} }
clang::CXXMethodDecl* method =
llvm::dyn_cast_or_null<clang::CXXMethodDecl>(callee);
if (method != NULL && method->isVirtual()) {
clang::CXXMemberCallExpr* memcall =
llvm::dyn_cast_or_null<clang::CXXMemberCallExpr>(call);
if (memcall != NULL) {
clang::CXXMethodDecl* target = method->getDevirtualizedMethod(
memcall->getImplicitObjectArgument(), false);
if (target != NULL) {
if (KnownToCauseGC(ctx_, target)) {
out.setGC();
}
} else {
if (SuspectedToCauseGC(ctx_, method)) {
out.setGC();
}
}
}
}
}
return out; return out;
} }
......
...@@ -183,12 +183,19 @@ end ...@@ -183,12 +183,19 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
local function ParseGNFile() local function ParseGNFile(for_test)
local result = {} local result = {}
local gn_files = { local gn_files
if for_test then
gn_files = {
{ "tools/gcmole/GCMOLE.gn", '"([^"]-%.cc)"', "" }
}
else
gn_files = {
{ "BUILD.gn", '"([^"]-%.cc)"', "" }, { "BUILD.gn", '"([^"]-%.cc)"', "" },
{ "test/cctest/BUILD.gn", '"(test-[^"]-%.cc)"', "test/cctest/" } { "test/cctest/BUILD.gn", '"(test-[^"]-%.cc)"', "test/cctest/" }
} }
end
for i = 1, #gn_files do for i = 1, #gn_files do
local filename = gn_files[i][1] local filename = gn_files[i][1]
...@@ -231,7 +238,8 @@ local function BuildFileList(sources, props) ...@@ -231,7 +238,8 @@ local function BuildFileList(sources, props)
end end
local gn_sources = ParseGNFile() local gn_sources = ParseGNFile(false)
local gn_test_sources = ParseGNFile(true)
local function FilesForArch(arch) local function FilesForArch(arch)
return BuildFileList(gn_sources, { os = 'linux', return BuildFileList(gn_sources, { os = 'linux',
...@@ -240,6 +248,13 @@ local function FilesForArch(arch) ...@@ -240,6 +248,13 @@ local function FilesForArch(arch)
simulator = ''}) simulator = ''})
end end
local function FilesForTest(arch)
return BuildFileList(gn_test_sources, { os = 'linux',
arch = arch,
mode = 'debug',
simulator = ''})
end
local mtConfig = {} local mtConfig = {}
mtConfig.__index = mtConfig mtConfig.__index = mtConfig
...@@ -393,8 +408,13 @@ end ...@@ -393,8 +408,13 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Analysis -- Analysis
local function CheckCorrectnessForArch(arch) local function CheckCorrectnessForArch(arch, for_test)
local files = FilesForArch(arch) local files
if for_test then
files = FilesForTest(arch)
else
files = FilesForArch(arch)
end
local cfg = ARCHITECTURES[arch] local cfg = ARCHITECTURES[arch]
if not FLAGS.reuse_gcsuspects then if not FLAGS.reuse_gcsuspects then
...@@ -403,6 +423,7 @@ local function CheckCorrectnessForArch(arch) ...@@ -403,6 +423,7 @@ local function CheckCorrectnessForArch(arch)
local processed_files = 0 local processed_files = 0
local errors_found = false local errors_found = false
local output = ""
local function SearchForErrors(filename, lines) local function SearchForErrors(filename, lines)
processed_files = processed_files + 1 processed_files = processed_files + 1
for l in lines do for l in lines do
...@@ -410,9 +431,13 @@ local function CheckCorrectnessForArch(arch) ...@@ -410,9 +431,13 @@ local function CheckCorrectnessForArch(arch)
l:match "^[^:]+:%d+:%d+:" or l:match "^[^:]+:%d+:%d+:" or
l:match "error" or l:match "error" or
l:match "warning" l:match "warning"
if for_test then
output = output.."\n"..l
else
print(l) print(l)
end end
end end
end
log("** Searching for evaluation order problems%s for %s", log("** Searching for evaluation order problems%s for %s",
FLAGS.dead_vars and " and dead variables" or "", FLAGS.dead_vars and " and dead variables" or "",
...@@ -427,18 +452,34 @@ local function CheckCorrectnessForArch(arch) ...@@ -427,18 +452,34 @@ local function CheckCorrectnessForArch(arch)
processed_files, processed_files,
errors_found and "Errors found" or "No errors found") errors_found and "Errors found" or "No errors found")
return errors_found return errors_found, output
end end
local function SafeCheckCorrectnessForArch(arch) local function SafeCheckCorrectnessForArch(arch, for_test)
local status, errors = pcall(CheckCorrectnessForArch, arch) local status, errors, output = pcall(CheckCorrectnessForArch, arch, for_test)
if not status then if not status then
print(string.format("There was an error: %s", errors)) print(string.format("There was an error: %s", errors))
errors = true errors = true
end end
return errors return errors, output
end end
local function TestRun()
local errors, output = SafeCheckCorrectnessForArch('x64', true)
local filename = "tools/gcmole/test-expectations.txt"
local exp_file = assert(io.open(filename), "failed to open test expectations file")
local expectations = exp_file:read('*all')
if output ~= expectations then
log("** Output mismatch from running tests. Please run them manually.")
else
log("** Tests ran successfully")
end
end
TestRun()
local errors = false local errors = false
for _, arch in ipairs(ARCHS) do for _, arch in ipairs(ARCHS) do
...@@ -446,7 +487,7 @@ for _, arch in ipairs(ARCHS) do ...@@ -446,7 +487,7 @@ for _, arch in ipairs(ARCHS) do
error ("Unknown arch: " .. arch) error ("Unknown arch: " .. arch)
end end
errors = SafeCheckCorrectnessForArch(arch, report) or errors errors = SafeCheckCorrectnessForArch(arch, false) or errors
end end
os.exit(errors and 1 or 0) os.exit(errors and 1 or 0)
IsConstructor
IsEval
IsAsync
IsPromiseAll
tools/gcmole/gcmole-test.cc:45:3: warning: Possible problem with evaluation order.
TwoArgumentsFunction(*CauseGC(obj1, isolate), *CauseGC(obj2, isolate));
^
tools/gcmole/gcmole-test.cc:57:3: warning: Possible problem with evaluation order.
TwoSizeTArgumentsFunction(sizeof(*CauseGC(obj1, isolate)),
^
tools/gcmole/gcmole-test.cc:82:7: warning: Possible problem with evaluation order.
so->Method(*CauseGC(obj1, isolate));
^
tools/gcmole/gcmole-test.cc:84:7: warning: Possible problem with evaluation order.
so->Method(CauseGCRaw(*obj1, isolate));
^
tools/gcmole/gcmole-test.cc:128:14: warning: Possible problem with evaluation order.
so_handle->Method(*derived.VirtualCauseGC(obj1, isolate));
^
tools/gcmole/gcmole-test.cc:130:14: warning: Possible problem with evaluation order.
so_handle->Method(*base->VirtualCauseGC(obj1, isolate));
^
6 warnings generated.
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