| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 #include "vm/coverage.h" | |
| 6 | |
| 7 #include "include/dart_api.h" | |
| 8 | |
| 9 #include "vm/compiler.h" | |
| 10 #include "vm/isolate.h" | |
| 11 #include "vm/json_stream.h" | |
| 12 #include "vm/longjump.h" | |
| 13 #include "vm/object.h" | |
| 14 #include "vm/object_store.h" | |
| 15 | |
| 16 namespace dart { | |
| 17 | |
| 18 DEFINE_FLAG(charp, coverage_dir, NULL, | |
| 19 "Enable writing coverage data into specified directory."); | |
| 20 | |
| 21 | |
| 22 class CoverageFilterAll : public CoverageFilter { | |
| 23 public: | |
| 24 bool ShouldOutputCoverageFor(const Library& lib, | |
| 25 const Script& script, | |
| 26 const Class& cls, | |
| 27 const Function& func) const { | |
| 28 return true; | |
| 29 } | |
| 30 }; | |
| 31 | |
| 32 | |
| 33 // map[token_pos] -> line-number. | |
| 34 static void ComputeTokenPosToLineNumberMap(const Script& script, | |
| 35 GrowableArray<intptr_t>* map) { | |
| 36 const TokenStream& tkns = TokenStream::Handle(script.tokens()); | |
| 37 const intptr_t len = ExternalTypedData::Handle(tkns.GetStream()).Length(); | |
| 38 map->SetLength(len); | |
| 39 #if defined(DEBUG) | |
| 40 for (intptr_t i = 0; i < len; i++) { | |
| 41 (*map)[i] = -1; | |
| 42 } | |
| 43 #endif | |
| 44 TokenStream::Iterator tkit(tkns, | |
| 45 TokenPosition::kMinSource, | |
| 46 TokenStream::Iterator::kAllTokens); | |
| 47 intptr_t cur_line = script.line_offset() + 1; | |
| 48 while (tkit.CurrentTokenKind() != Token::kEOS) { | |
| 49 const intptr_t position = tkit.CurrentPosition().Pos(); | |
| 50 (*map)[position] = cur_line; | |
| 51 if (tkit.CurrentTokenKind() == Token::kNEWLINE) { | |
| 52 cur_line++; | |
| 53 } | |
| 54 tkit.Advance(); | |
| 55 } | |
| 56 } | |
| 57 | |
| 58 | |
| 59 void CodeCoverage::CompileAndAdd(const Function& function, | |
| 60 const JSONArray& hits_or_sites, | |
| 61 const GrowableArray<intptr_t>& pos_to_line, | |
| 62 bool as_call_sites) { | |
| 63 if (!FLAG_support_coverage) { | |
| 64 return; | |
| 65 } | |
| 66 // If the function should not be compiled for coverage analysis, then just | |
| 67 // skip this method. | |
| 68 // TODO(iposva): Maybe we should skip synthesized methods in general too. | |
| 69 if (function.is_abstract() || function.IsRedirectingFactory()) { | |
| 70 return; | |
| 71 } | |
| 72 if (function.IsNonImplicitClosureFunction() && | |
| 73 (function.context_scope() == ContextScope::null())) { | |
| 74 // TODO(iposva): This can arise if we attempt to compile an inner function | |
| 75 // before we have compiled its enclosing function or if the enclosing | |
| 76 // function failed to compile. | |
| 77 return; | |
| 78 } | |
| 79 Thread* thread = Thread::Current(); | |
| 80 Zone* zone = thread->zone(); | |
| 81 // Make sure we have the unoptimized code for this function available. | |
| 82 if (Compiler::EnsureUnoptimizedCode(thread, function) != Error::null()) { | |
| 83 // Ignore the error and this function entirely. | |
| 84 return; | |
| 85 } | |
| 86 const Code& code = Code::Handle(zone, function.unoptimized_code()); | |
| 87 ASSERT(!code.IsNull()); | |
| 88 | |
| 89 // Print the hit counts for all IC datas. | |
| 90 ZoneGrowableArray<const ICData*>* ic_data_array = | |
| 91 new(zone) ZoneGrowableArray<const ICData*>(); | |
| 92 function.RestoreICDataMap(ic_data_array, false /* clone ic-data */); | |
| 93 const PcDescriptors& descriptors = PcDescriptors::Handle( | |
| 94 zone, code.pc_descriptors()); | |
| 95 | |
| 96 const TokenPosition begin_pos = function.token_pos(); | |
| 97 const TokenPosition end_pos = function.end_token_pos(); | |
| 98 intptr_t last_line = -1; | |
| 99 intptr_t last_count = 0; | |
| 100 // Only IC based calls have counting. | |
| 101 PcDescriptors::Iterator iter(descriptors, | |
| 102 RawPcDescriptors::kIcCall | RawPcDescriptors::kUnoptStaticCall); | |
| 103 while (iter.MoveNext()) { | |
| 104 HANDLESCOPE(thread); | |
| 105 const ICData* ic_data = (*ic_data_array)[iter.DeoptId()]; | |
| 106 if (!ic_data->IsNull()) { | |
| 107 const TokenPosition token_pos = iter.TokenPos(); | |
| 108 // Filter out descriptors that do not map to tokens in the source code. | |
| 109 if ((token_pos < begin_pos) || (token_pos > end_pos)) { | |
| 110 continue; | |
| 111 } | |
| 112 if (as_call_sites) { | |
| 113 bool is_static_call = iter.Kind() == RawPcDescriptors::kUnoptStaticCall; | |
| 114 ic_data->PrintToJSONArray(hits_or_sites, | |
| 115 token_pos, | |
| 116 is_static_call); | |
| 117 } else { | |
| 118 intptr_t line = pos_to_line[token_pos.Pos()]; | |
| 119 #if defined(DEBUG) | |
| 120 const Script& script = Script::Handle(zone, function.script()); | |
| 121 intptr_t test_line = -1; | |
| 122 script.GetTokenLocation(token_pos, &test_line, NULL); | |
| 123 ASSERT(test_line == line); | |
| 124 #endif | |
| 125 // Merge hit data where possible. | |
| 126 if (last_line == line) { | |
| 127 last_count += ic_data->AggregateCount(); | |
| 128 } else { | |
| 129 if ((last_line != -1)) { | |
| 130 hits_or_sites.AddValue(last_line); | |
| 131 hits_or_sites.AddValue(last_count); | |
| 132 } | |
| 133 last_count = ic_data->AggregateCount(); | |
| 134 last_line = line; | |
| 135 } | |
| 136 } | |
| 137 } | |
| 138 } | |
| 139 // Write last hit value if needed. | |
| 140 if (!as_call_sites && (last_line != -1)) { | |
| 141 hits_or_sites.AddValue(last_line); | |
| 142 hits_or_sites.AddValue(last_count); | |
| 143 } | |
| 144 } | |
| 145 | |
| 146 | |
| 147 void CodeCoverage::PrintClass(const Library& lib, | |
| 148 const Class& cls, | |
| 149 const JSONArray& jsarr, | |
| 150 CoverageFilter* filter, | |
| 151 bool as_call_sites) { | |
| 152 if (!FLAG_support_coverage) { | |
| 153 return; | |
| 154 } | |
| 155 Thread* thread = Thread::Current(); | |
| 156 if (cls.EnsureIsFinalized(thread) != Error::null()) { | |
| 157 // Only classes that have been finalized do have a meaningful list of | |
| 158 // functions. | |
| 159 return; | |
| 160 } | |
| 161 Array& functions = Array::Handle(cls.functions()); | |
| 162 ASSERT(!functions.IsNull()); | |
| 163 Function& function = Function::Handle(); | |
| 164 Script& script = Script::Handle(); | |
| 165 String& saved_url = String::Handle(); | |
| 166 String& url = String::Handle(); | |
| 167 GrowableArray<intptr_t> pos_to_line; | |
| 168 int i = 0; | |
| 169 while (i < functions.Length()) { | |
| 170 HANDLESCOPE(thread); | |
| 171 function ^= functions.At(i); | |
| 172 script = function.script(); | |
| 173 saved_url = script.url(); | |
| 174 if (!filter->ShouldOutputCoverageFor(lib, script, cls, function)) { | |
| 175 i++; | |
| 176 continue; | |
| 177 } | |
| 178 if (!as_call_sites) { | |
| 179 ComputeTokenPosToLineNumberMap(script, &pos_to_line); | |
| 180 } | |
| 181 JSONObject jsobj(&jsarr); | |
| 182 jsobj.AddProperty("source", saved_url.ToCString()); | |
| 183 jsobj.AddProperty("script", script); | |
| 184 JSONArray hits_or_sites(&jsobj, as_call_sites ? "callSites" : "hits"); | |
| 185 | |
| 186 // We stay within this loop while we are seeing functions from the same | |
| 187 // source URI. | |
| 188 while (i < functions.Length()) { | |
| 189 function ^= functions.At(i); | |
| 190 script = function.script(); | |
| 191 url = script.url(); | |
| 192 if (!url.Equals(saved_url)) { | |
| 193 pos_to_line.Clear(); | |
| 194 break; | |
| 195 } | |
| 196 if (!filter->ShouldOutputCoverageFor(lib, script, cls, function)) { | |
| 197 i++; | |
| 198 continue; | |
| 199 } | |
| 200 CompileAndAdd(function, hits_or_sites, pos_to_line, as_call_sites); | |
| 201 i++; | |
| 202 } | |
| 203 } | |
| 204 | |
| 205 // TODO(turnidge): This looks like it prints closures many, many times. | |
| 206 const GrowableObjectArray& closures = GrowableObjectArray::Handle( | |
| 207 thread->isolate()->object_store()->closure_functions()); | |
| 208 pos_to_line.Clear(); | |
| 209 // We need to keep rechecking the length of the closures array, as handling | |
| 210 // a closure potentially adds new entries to the end. | |
| 211 i = 0; | |
| 212 while (i < closures.Length()) { | |
| 213 HANDLESCOPE(thread); | |
| 214 function ^= closures.At(i); | |
| 215 if (function.Owner() != cls.raw()) { | |
| 216 i++; | |
| 217 continue; | |
| 218 } | |
| 219 script = function.script(); | |
| 220 saved_url = script.url(); | |
| 221 if (!filter->ShouldOutputCoverageFor(lib, script, cls, function)) { | |
| 222 i++; | |
| 223 continue; | |
| 224 } | |
| 225 ComputeTokenPosToLineNumberMap(script, &pos_to_line); | |
| 226 JSONObject jsobj(&jsarr); | |
| 227 jsobj.AddProperty("source", saved_url.ToCString()); | |
| 228 jsobj.AddProperty("script", script); | |
| 229 JSONArray hits_or_sites(&jsobj, as_call_sites ? "callSites" : "hits"); | |
| 230 | |
| 231 // We stay within this loop while we are seeing functions from the same | |
| 232 // source URI. | |
| 233 while (i < closures.Length()) { | |
| 234 function ^= closures.At(i); | |
| 235 script = function.script(); | |
| 236 url = script.url(); | |
| 237 if (!url.Equals(saved_url)) { | |
| 238 pos_to_line.Clear(); | |
| 239 break; | |
| 240 } | |
| 241 CompileAndAdd(function, hits_or_sites, pos_to_line, as_call_sites); | |
| 242 i++; | |
| 243 } | |
| 244 } | |
| 245 } | |
| 246 | |
| 247 | |
| 248 void CodeCoverage::Write(Thread* thread) { | |
| 249 if (!FLAG_support_coverage) { | |
| 250 return; | |
| 251 } | |
| 252 if (FLAG_coverage_dir == NULL) { | |
| 253 return; | |
| 254 } | |
| 255 | |
| 256 Dart_FileOpenCallback file_open = Dart::file_open_callback(); | |
| 257 Dart_FileWriteCallback file_write = Dart::file_write_callback(); | |
| 258 Dart_FileCloseCallback file_close = Dart::file_close_callback(); | |
| 259 if ((file_open == NULL) || (file_write == NULL) || (file_close == NULL)) { | |
| 260 return; | |
| 261 } | |
| 262 | |
| 263 JSONStream stream; | |
| 264 PrintJSON(thread, &stream, NULL, false); | |
| 265 | |
| 266 intptr_t pid = OS::ProcessId(); | |
| 267 char* filename = OS::SCreate(thread->zone(), | |
| 268 "%s/dart-cov-%" Pd "-%" Pd64 ".json", | |
| 269 FLAG_coverage_dir, pid, thread->isolate()->main_port()); | |
| 270 void* file = (*file_open)(filename, true); | |
| 271 if (file == NULL) { | |
| 272 OS::Print("Failed to write coverage file: %s\n", filename); | |
| 273 return; | |
| 274 } | |
| 275 (*file_write)(stream.buffer()->buf(), stream.buffer()->length(), file); | |
| 276 (*file_close)(file); | |
| 277 } | |
| 278 | |
| 279 | |
| 280 void CodeCoverage::PrintJSON(Thread* thread, | |
| 281 JSONStream* stream, | |
| 282 CoverageFilter* filter, | |
| 283 bool as_call_sites) { | |
| 284 if (!FLAG_support_coverage) { | |
| 285 return; | |
| 286 } | |
| 287 CoverageFilterAll default_filter; | |
| 288 if (filter == NULL) { | |
| 289 filter = &default_filter; | |
| 290 } | |
| 291 const GrowableObjectArray& libs = GrowableObjectArray::Handle( | |
| 292 thread->zone(), | |
| 293 thread->isolate()->object_store()->libraries()); | |
| 294 Library& lib = Library::Handle(); | |
| 295 Class& cls = Class::Handle(); | |
| 296 JSONObject coverage(stream); | |
| 297 coverage.AddProperty("type", "CodeCoverage"); | |
| 298 { | |
| 299 JSONArray jsarr(&coverage, "coverage"); | |
| 300 for (int i = 0; i < libs.Length(); i++) { | |
| 301 lib ^= libs.At(i); | |
| 302 ClassDictionaryIterator it(lib, ClassDictionaryIterator::kIteratePrivate); | |
| 303 while (it.HasNext()) { | |
| 304 cls = it.GetNextClass(); | |
| 305 ASSERT(!cls.IsNull()); | |
| 306 PrintClass(lib, cls, jsarr, filter, as_call_sites); | |
| 307 } | |
| 308 } | |
| 309 } | |
| 310 } | |
| 311 | |
| 312 | |
| 313 } // namespace dart | |
| OLD | NEW |