// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "vm/coverage.h" #include "include/dart_api.h" #include "vm/compiler.h" #include "vm/isolate.h" #include "vm/json_stream.h" #include "vm/object.h" #include "vm/object_store.h" namespace dart { DEFINE_FLAG(charp, coverage_dir, NULL, "Enable writing coverage data into specified directory."); void CodeCoverage::CompileAndAdd(const Function& function, const JSONArray& hits_arr) { if (!function.HasCode()) { // If the function should not be compiled or if the compilation failed, // then just skip this method. // TODO(iposva): Maybe we should skip synthesized methods in general too. if (function.is_abstract() || function.IsRedirectingFactory()) { return; } if (function.IsNonImplicitClosureFunction() && (function.context_scope() == ContextScope::null())) { // TODO(iposva): This can arise if we attempt to compile an inner function // before we have compiled its enclosing function or if the enclosing // function failed to compile. OS::Print("### Coverage skipped compiling: %s\n", function.ToCString()); return; } const Error& err = Error::Handle(Compiler::CompileFunction(function)); if (!err.IsNull()) { OS::Print("### Coverage failed compiling:\n%s\n", err.ToErrorCString()); return; } } ASSERT(function.HasCode()); Isolate* isolate = Isolate::Current(); // Print the hit counts for all IC datas. const Script& script = Script::Handle(function.script()); const Code& code = Code::Handle(function.unoptimized_code()); const Array& ic_array = Array::Handle(code.ExtractTypeFeedbackArray()); const PcDescriptors& descriptors = PcDescriptors::Handle( code.pc_descriptors()); ICData& ic_data = ICData::Handle(); for (int j = 0; j < descriptors.Length(); j++) { HANDLESCOPE(isolate); PcDescriptors::Kind kind = descriptors.DescriptorKind(j); // Only IC based calls have counting. if ((kind == PcDescriptors::kIcCall) || (kind == PcDescriptors::kUnoptStaticCall)) { intptr_t deopt_id = descriptors.DeoptId(j); ic_data ^= ic_array.At(deopt_id); if (!ic_data.IsNull()) { intptr_t token_pos = descriptors.TokenPos(j); intptr_t line = -1; script.GetTokenLocation(token_pos, &line, NULL); hits_arr.AddValue(line); hits_arr.AddValue(ic_data.AggregateCount()); } } } } void CodeCoverage::PrintClass(const Class& cls, const JSONArray& jsarr) { Isolate* isolate = Isolate::Current(); Array& functions = Array::Handle(cls.functions()); ASSERT(!functions.IsNull()); Function& function = Function::Handle(); Script& script = Script::Handle(); String& saved_url = String::Handle(); String& url = String::Handle(); int i = 0; while (i < functions.Length()) { HANDLESCOPE(isolate); function ^= functions.At(i); JSONObject jsobj(&jsarr); script = function.script(); saved_url = script.url(); jsobj.AddProperty("source", saved_url.ToCString()); JSONArray hits_arr(&jsobj, "hits"); // We stay within this loop while we are seeing functions from the same // source URI. while (i < functions.Length()) { function ^= functions.At(i); script = function.script(); url = script.url(); if (!url.Equals(saved_url)) { break; } CompileAndAdd(function, hits_arr); i++; } } GrowableObjectArray& closures = GrowableObjectArray::Handle(cls.closures()); if (!closures.IsNull()) { i = 0; // We need to keep rechecking the length of the closures array, as handling // a closure potentially adds new entries to the end. while (i < closures.Length()) { HANDLESCOPE(isolate); function ^= closures.At(i); JSONObject jsobj(&jsarr); script = function.script(); saved_url = script.url(); jsobj.AddProperty("source", saved_url.ToCString()); JSONArray hits_arr(&jsobj, "hits"); // We stay within this loop while we are seeing functions from the same // source URI. while (i < closures.Length()) { function ^= closures.At(i); script = function.script(); url = script.url(); if (!url.Equals(saved_url)) { break; } CompileAndAdd(function, hits_arr); i++; } } } } void CodeCoverage::Write(Isolate* isolate) { if (FLAG_coverage_dir == NULL) { return; } Dart_FileOpenCallback file_open = Isolate::file_open_callback(); Dart_FileWriteCallback file_write = Isolate::file_write_callback(); Dart_FileCloseCallback file_close = Isolate::file_close_callback(); if ((file_open == NULL) || (file_write == NULL) || (file_close == NULL)) { return; } JSONStream stream; { const GrowableObjectArray& libs = GrowableObjectArray::Handle( isolate, isolate->object_store()->libraries()); Library& lib = Library::Handle(); Class& cls = Class::Handle(); JSONArray jsarr(&stream); for (int i = 0; i < libs.Length(); i++) { lib ^= libs.At(i); ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate); while (it.HasNext()) { cls = it.GetNextClass(); if (cls.EnsureIsFinalized(isolate) == Error::null()) { // Only classes that have been finalized do have a meaningful list of // functions. PrintClass(cls, jsarr); } } } } const char* format = "%s/dart-cov-%" Pd "-%" Pd ".json"; intptr_t pid = OS::ProcessId(); intptr_t len = OS::SNPrint(NULL, 0, format, FLAG_coverage_dir, pid, isolate->main_port()); char* filename = Isolate::Current()->current_zone()->Alloc(len + 1); OS::SNPrint(filename, len + 1, format, FLAG_coverage_dir, pid, isolate->main_port()); void* file = (*file_open)(filename, true); if (file == NULL) { OS::Print("Failed to write coverage file: %s\n", filename); return; } (*file_write)(stream.buffer()->buf(), stream.buffer()->length(), file); (*file_close)(file); } } // namespace dart