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 |