v8/tools/gcmole/gcmole.lua
machenbach 994dc21148 [gn] Use one source of truth for test source files.
This avoids forgetting to add files for either gyp or gn.

While for most executables, this is detected by compilation
errors, for test executables, it can lead to tests silently
not running.

BUG=chromium:474921

Review-Url: https://codereview.chromium.org/2098313002
Cr-Commit-Position: refs/heads/master@{#37331}
2016-06-28 13:24:08 +00:00

453 lines
14 KiB
Lua

-- Copyright 2011 the V8 project authors. All rights reserved.
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are
-- met:
--
-- * Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above
-- copyright notice, this list of conditions and the following
-- disclaimer in the documentation and/or other materials provided
-- with the distribution.
-- * Neither the name of Google Inc. nor the names of its
-- contributors may be used to endorse or promote products derived
-- from this software without specific prior written permission.
--
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-- "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-- LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-- A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-- OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-- SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-- LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-- DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-- THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-- This is main driver for gcmole tool. See README for more details.
-- Usage: CLANG_BIN=clang-bin-dir lua tools/gcmole/gcmole.lua [arm|ia32|x64]
local DIR = arg[0]:match("^(.+)/[^/]+$")
local FLAGS = {
-- Do not build gcsuspects file and reuse previously generated one.
reuse_gcsuspects = false;
-- Don't use parallel python runner.
sequential = 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', 'arm64' }
local io = require "io"
local os = require "os"
function log(...)
io.stderr:write(string.format(...))
io.stderr:write "\n"
end
-------------------------------------------------------------------------------
-- Clang invocation
local CLANG_BIN = os.getenv "CLANG_BIN"
local CLANG_PLUGINS = os.getenv "CLANG_PLUGINS"
if not CLANG_BIN or CLANG_BIN == "" then
error "CLANG_BIN not set"
end
if not CLANG_PLUGINS or CLANG_PLUGINS == "" then
CLANG_PLUGINS = DIR
end
local function MakeClangCommandLine(
plugin, plugin_args, triple, arch_define, arch_options)
if plugin_args then
for i = 1, #plugin_args do
plugin_args[i] = "-Xclang -plugin-arg-" .. plugin
.. " -Xclang " .. plugin_args[i]
end
plugin_args = " " .. table.concat(plugin_args, " ")
end
return CLANG_BIN .. "/clang++ -std=c++11 -c "
.. " -Xclang -load -Xclang " .. CLANG_PLUGINS .. "/libgcmole.so"
.. " -Xclang -plugin -Xclang " .. plugin
.. (plugin_args or "")
.. " -Xclang -triple -Xclang " .. triple
.. " -D" .. arch_define
.. " -DENABLE_DEBUGGER_SUPPORT"
.. " -DV8_I18N_SUPPORT"
.. " -I./"
.. " -Iinclude/"
.. " -Ithird_party/icu/source/common"
.. " -Ithird_party/icu/source/i18n"
.. " " .. arch_options
end
local function IterTable(t)
return coroutine.wrap(function ()
for i, v in ipairs(t) do
coroutine.yield(v)
end
end)
end
local function SplitResults(lines, func)
-- Splits the output of parallel.py and calls func on each result.
-- Bails out in case of an error in one of the executions.
local current = {}
local filename = ""
for line in lines do
local new_file = line:match "^______________ (.*)$"
local code = line:match "^______________ finish (%d+) ______________$"
if code then
if tonumber(code) > 0 then
log(table.concat(current, "\n"))
log("Failed to examine " .. filename)
return false
end
log("-- %s", filename)
func(filename, IterTable(current))
elseif new_file then
filename = new_file
current = {}
else
table.insert(current, line)
end
end
return true
end
function InvokeClangPluginForEachFile(filenames, cfg, func)
local cmd_line = MakeClangCommandLine(cfg.plugin,
cfg.plugin_args,
cfg.triple,
cfg.arch_define,
cfg.arch_options)
if FLAGS.sequential then
log("** Sequential execution.")
for _, filename in ipairs(filenames) do
log("-- %s", filename)
local action = cmd_line .. " " .. filename .. " 2>&1"
if FLAGS.verbose then print('popen ', action) end
local pipe = io.popen(action)
func(filename, pipe:lines())
local success = pipe:close()
if not success then error("Failed to run: " .. action) end
end
else
log("** Parallel execution.")
local action = "python tools/gcmole/parallel.py \""
.. cmd_line .. "\" " .. table.concat(filenames, " ")
if FLAGS.verbose then print('popen ', action) end
local pipe = io.popen(action)
local success = SplitResults(pipe:lines(), func)
local closed = pipe:close()
if not (success and closed) then error("Failed to run: " .. action) end
end
end
-------------------------------------------------------------------------------
-- GYP file parsing
local function ParseGYPFile()
local result = {}
local gyp_files = {
{ "src/v8.gyp", "'([^']-%.cc)'", "src/" },
{ "test/cctest/cctest.gyp", "'(test-[^']-%.cc)'", "test/cctest/" }
}
for i = 1, #gyp_files do
local filename = gyp_files[i][1]
local pattern = gyp_files[i][2]
local prefix = gyp_files[i][3]
local gyp_file = assert(io.open(filename), "failed to open GYP file")
local gyp = gyp_file:read('*a')
for condition, sources in
gyp:gmatch "%[.-### gcmole%((.-)%) ###(.-)%]" do
if result[condition] == nil then result[condition] = {} end
for file in sources:gmatch(pattern) do
table.insert(result[condition], prefix .. file)
end
end
gyp_file:close()
end
return result
end
local function EvaluateCondition(cond, props)
if cond == 'all' then return true end
local p, v = cond:match "(%w+):(%w+)"
assert(p and v, "failed to parse condition: " .. cond)
assert(props[p] ~= nil, "undefined configuration property: " .. p)
return props[p] == v
end
local function BuildFileList(sources, props)
local list = {}
for condition, files in pairs(sources) do
if EvaluateCondition(condition, props) then
for i = 1, #files do table.insert(list, files[i]) end
end
end
return list
end
local sources = ParseGYPFile()
local function FilesForArch(arch)
return BuildFileList(sources, { os = 'linux',
arch = arch,
mode = 'debug',
simulator = ''})
end
local mtConfig = {}
mtConfig.__index = mtConfig
local function config (t) return setmetatable(t, mtConfig) end
function mtConfig:extend(t)
local e = {}
for k, v in pairs(self) do e[k] = v end
for k, v in pairs(t) do e[k] = v end
return config(e)
end
local ARCHITECTURES = {
ia32 = config { triple = "i586-unknown-linux",
arch_define = "V8_TARGET_ARCH_IA32",
arch_options = "-m32" },
arm = config { triple = "i586-unknown-linux",
arch_define = "V8_TARGET_ARCH_ARM",
arch_options = "-m32" },
x64 = config { triple = "x86_64-unknown-linux",
arch_define = "V8_TARGET_ARCH_X64",
arch_options = "" },
arm64 = config { triple = "x86_64-unknown-linux",
arch_define = "V8_TARGET_ARCH_ARM64",
arch_options = "" },
}
-------------------------------------------------------------------------------
-- 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",
-- Ignore all StateTag methods.
"StateTag",
-- Ignore printing of elements transition.
"PrintElementsTransition"
};
local function AddCause(name, cause)
local t = gc_caused[name]
if not t then
t = {}
gc_caused[name] = t
end
table.insert(t, cause)
end
local function resolve(name)
local f = funcs[name]
if not f then
f = {}
funcs[name] = f
if name:match "Collect.*Garbage" then
gc[name] = true
AddCause(name, "<GC>")
end
if FLAGS.whitelist then
for i = 1, #WHITELIST do
if name:match(WHITELIST[i]) then
gc[name] = false
end
end
end
end
return f
end
local function parse (filename, lines)
local scope
for funcname in lines do
if funcname:sub(1, 1) ~= '\t' then
resolve(funcname)
scope = funcname
else
local name = funcname:sub(2)
resolve(name)[scope] = true
end
end
end
local function propagate ()
log "** Propagating GC information"
local function mark(from, callers)
for caller, _ in pairs(callers) do
if gc[caller] == nil then
gc[caller] = true
mark(caller, funcs[caller])
end
AddCause(caller, from)
end
end
for funcname, callers in pairs(funcs) do
if gc[funcname] then mark(funcname, callers) end
end
end
local function GenerateGCSuspects(arch, files, cfg)
-- Reset the global state.
gc, gc_caused, funcs = {}, {}, {}
log ("** Building GC Suspects for %s", arch)
InvokeClangPluginForEachFile (files,
cfg:extend { plugin = "dump-callees" },
parse)
propagate()
local out = assert(io.open("gcsuspects", "w"))
for name, value in pairs(gc) do if value then out:write (name, '\n') end end
out:close()
local out = assert(io.open("gccauses", "w"))
out:write "GC = {"
for name, causes in pairs(gc_caused) do
out:write("['", name, "'] = {")
for i = 1, #causes do out:write ("'", causes[i], "';") end
out:write("};\n")
end
out:write "}"
out:close()
log ("** GCSuspects generated for %s", arch)
end
--------------------------------------------------------------------------------
-- Analysis
local function CheckCorrectnessForArch(arch)
local files = FilesForArch(arch)
local cfg = ARCHITECTURES[arch]
if not FLAGS.reuse_gcsuspects then
GenerateGCSuspects(arch, files, cfg)
end
local processed_files = 0
local errors_found = false
local function SearchForErrors(filename, lines)
processed_files = processed_files + 1
for l in lines do
errors_found = errors_found or
l:match "^[^:]+:%d+:%d+:" or
l:match "error" or
l:match "warning"
print(l)
end
end
log("** Searching for evaluation order problems%s for %s",
FLAGS.dead_vars and " and dead variables" or "",
arch)
local plugin_args
if FLAGS.dead_vars then plugin_args = { "--dead-vars" } end
InvokeClangPluginForEachFile(files,
cfg:extend { plugin = "find-problems",
plugin_args = plugin_args },
SearchForErrors)
log("** Done processing %d files. %s",
processed_files,
errors_found and "Errors found" or "No errors found")
return errors_found
end
local function SafeCheckCorrectnessForArch(arch)
local status, errors = pcall(CheckCorrectnessForArch, arch)
if not status then
print(string.format("There was an error: %s", errors))
errors = true
end
return errors
end
local errors = false
for _, arch in ipairs(ARCHS) do
if not ARCHITECTURES[arch] then
error ("Unknown arch: " .. arch)
end
errors = SafeCheckCorrectnessForArch(arch, report) or errors
end
os.exit(errors and 1 or 0)