1// Copyright (c) 2017, 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 "platform/globals.h"
6
7#if defined(DART_USE_TCMALLOC) && !defined(PRODUCT)
8
9#include "platform/assert.h"
10#include "vm/globals.h"
11#include "vm/malloc_hooks.h"
12#include "vm/os.h"
13#include "vm/profiler.h"
14#include "vm/profiler_service.h"
15#include "vm/unit_test.h"
16
17namespace dart {
18
19static void MallocHookTestBufferInitializer(volatile char* buffer,
20 uintptr_t size) {
21 // Run through the buffer and do something. If we don't do this and the memory
22 // in buffer isn't touched, the tcmalloc hooks won't be called.
23 for (uintptr_t i = 0; i < size; ++i) {
24 buffer[i] = i;
25 }
26}
27
28class EnableMallocHooksScope : public ValueObject {
29 public:
30 EnableMallocHooksScope() {
31 OSThread::Current(); // Ensure not allocated during test.
32 saved_enable_malloc_hooks_ = FLAG_profiler_native_memory;
33 FLAG_profiler_native_memory = true;
34 MallocHooks::Init();
35 MallocHooks::ResetStats();
36 }
37
38 ~EnableMallocHooksScope() {
39 MallocHooks::Cleanup();
40 FLAG_profiler_native_memory = saved_enable_malloc_hooks_;
41 }
42
43 private:
44 bool saved_enable_malloc_hooks_;
45};
46
47class EnableMallocHooksAndStacksScope : public EnableMallocHooksScope {
48 public:
49 EnableMallocHooksAndStacksScope() {
50 OSThread::Current(); // Ensure not allocated during test.
51 saved_enable_stack_traces_ = MallocHooks::stack_trace_collection_enabled();
52 MallocHooks::set_stack_trace_collection_enabled(true);
53 if (!FLAG_profiler) {
54 FLAG_profiler = true;
55 Profiler::Init();
56 }
57 MallocHooks::ResetStats();
58 }
59
60 ~EnableMallocHooksAndStacksScope() {
61 MallocHooks::set_stack_trace_collection_enabled(saved_enable_stack_traces_);
62 }
63
64 private:
65 bool saved_enable_stack_traces_;
66};
67
68UNIT_TEST_CASE(BasicMallocHookTest) {
69 EnableMallocHooksScope scope;
70
71 EXPECT_EQ(0L, MallocHooks::allocation_count());
72 EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
73 const intptr_t buffer_size = 10;
74 char* buffer = new char[buffer_size];
75 MallocHookTestBufferInitializer(buffer, buffer_size);
76
77 EXPECT_EQ(1L, MallocHooks::allocation_count());
78 EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size),
79 MallocHooks::heap_allocated_memory_in_bytes());
80
81 delete[] buffer;
82 EXPECT_EQ(0L, MallocHooks::allocation_count());
83 EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
84}
85
86UNIT_TEST_CASE(FreeUnseenMemoryMallocHookTest) {
87 EnableMallocHooksScope scope;
88
89 const intptr_t pre_hook_buffer_size = 3;
90 char* pre_hook_buffer = new char[pre_hook_buffer_size];
91 MallocHookTestBufferInitializer(pre_hook_buffer, pre_hook_buffer_size);
92
93 MallocHooks::ResetStats();
94 EXPECT_EQ(0L, MallocHooks::allocation_count());
95 EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
96
97 const intptr_t buffer_size = 10;
98 char* buffer = new char[buffer_size];
99 MallocHookTestBufferInitializer(buffer, buffer_size);
100
101 EXPECT_EQ(1L, MallocHooks::allocation_count());
102 EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size),
103 MallocHooks::heap_allocated_memory_in_bytes());
104
105 delete[] pre_hook_buffer;
106 EXPECT_EQ(1L, MallocHooks::allocation_count());
107 EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size),
108 MallocHooks::heap_allocated_memory_in_bytes());
109
110 delete[] buffer;
111 EXPECT_EQ(0L, MallocHooks::allocation_count());
112 EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
113}
114
115VM_UNIT_TEST_CASE(StackTraceMallocHookSimpleTest) {
116 EnableMallocHooksAndStacksScope scope;
117
118 char* var = static_cast<char*>(malloc(16 * sizeof(char)));
119 Sample* sample = MallocHooks::GetSample(var);
120 EXPECT(sample != NULL);
121
122 free(var);
123 sample = MallocHooks::GetSample(var);
124 EXPECT(sample == NULL);
125}
126
127static char* DART_NOINLINE StackTraceLengthHelper(uintptr_t* end_address) {
128 char* var = static_cast<char*>(malloc(16 * sizeof(char)));
129 *end_address = OS::GetProgramCounter();
130 return var;
131}
132
133VM_UNIT_TEST_CASE(StackTraceMallocHookLengthTest) {
134 EnableMallocHooksAndStacksScope scope;
135
136 uintptr_t test_start_address =
137 reinterpret_cast<uintptr_t>(Dart_TestStackTraceMallocHookLengthTest);
138 uintptr_t helper_start_address =
139 reinterpret_cast<uintptr_t>(StackTraceLengthHelper);
140 uintptr_t helper_end_address = 0;
141
142 char* var = StackTraceLengthHelper(&helper_end_address);
143 Sample* sample = MallocHooks::GetSample(var);
144 EXPECT(sample != NULL);
145 uintptr_t test_end_address = OS::GetProgramCounter();
146
147 // Ensure that all stack frames are where we expect them to be in the sample.
148 // If they aren't, the kSkipCount constant in malloc_hooks.cc is likely
149 // incorrect.
150 uword address = sample->At(0);
151 bool first_result =
152 (helper_start_address <= address) && (helper_end_address >= address);
153 EXPECT(first_result);
154 address = sample->At(1);
155 bool second_result =
156 (test_start_address <= address) && (test_end_address >= address);
157 EXPECT(second_result);
158
159 if (!(first_result && second_result)) {
160 OS::PrintErr(
161 "If this test is failing, it's likely that the value set for"
162 " the number of frames to skip in malloc_hooks.cc is "
163 "incorrect for this configuration/platform. This value can be"
164 " found in malloc_hooks.cc in the AllocationInfo class, and "
165 "is stored in the kSkipCount constant.\n");
166 OS::PrintErr("First result: %d Second Result: %d\n", first_result,
167 second_result);
168 OS::PrintErr("Dumping sample stack trace:\n");
169 sample->DumpStackTrace();
170 }
171
172 free(var);
173}
174
175ISOLATE_UNIT_TEST_CASE(StackTraceMallocHookSimpleJSONTest) {
176 EnableMallocHooksAndStacksScope scope;
177
178 ClearProfileVisitor cpv(Isolate::Current());
179 Profiler::sample_buffer()->VisitSamples(&cpv);
180
181 char* var = static_cast<char*>(malloc(16 * sizeof(char)));
182 JSONStream js;
183 ProfilerService::PrintNativeAllocationJSON(&js, -1, -1, false);
184 const char* json = js.ToCString();
185
186 // Check that all the stack frames from the current down to main are actually
187 // present in the profile. This is just a simple sanity check to make sure
188 // that the ProfileTrie has a representation of the stack trace collected when
189 // var is allocated. More intense testing is already done in profiler_test.cc.
190 // This is brittle: inlining and ICF in the C compiler and linker will affect
191 // the frames we see.
192 EXPECT_SUBSTRING("\"dart::Dart_TestStackTraceMallocHookSimpleJSONTest()\"",
193 json);
194 EXPECT_SUBSTRING("\"dart::TestCase::Run()\"", json);
195 EXPECT_SUBSTRING("\"dart::TestCaseBase::RunTest()\"", json);
196 EXPECT_SUBSTRING("\"main\"", json);
197
198 free(var);
199}
200
201}; // namespace dart
202
203#endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT)
204