1// Copyright 2009 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "src/logging/log-utils.h"
6
7#include <atomic>
8#include <memory>
9
10#include "src/base/platform/mutex.h"
11#include "src/base/platform/platform.h"
12#include "src/base/strings.h"
13#include "src/base/vector.h"
14#include "src/common/assert-scope.h"
15#include "src/common/globals.h"
16#include "src/execution/isolate-utils.h"
17#include "src/objects/objects-inl.h"
18#include "src/objects/string-inl.h"
19#include "src/strings/string-stream.h"
20#include "src/utils/version.h"
21
22namespace v8 {
23namespace internal {
24
25const char* const Log::kLogToTemporaryFile = "+";
26const char* const Log::kLogToConsole = "-";
27
28// static
29FILE* Log::CreateOutputHandle(std::string file_name) {
30  // If we're logging anything, we need to open the log file.
31  if (!FLAG_log) {
32    return nullptr;
33  } else if (Log::IsLoggingToConsole(file_name)) {
34    return stdout;
35  } else if (Log::IsLoggingToTemporaryFile(file_name)) {
36    return base::OS::OpenTemporaryFile();
37  } else {
38    return base::OS::FOpen(file_name.c_str(), base::OS::LogFileOpenMode);
39  }
40}
41
42// static
43bool Log::IsLoggingToConsole(std::string file_name) {
44  return file_name.compare(Log::kLogToConsole) == 0;
45}
46
47// static
48bool Log::IsLoggingToTemporaryFile(std::string file_name) {
49  return file_name.compare(Log::kLogToTemporaryFile) == 0;
50}
51
52Log::Log(Logger* logger, std::string file_name)
53    : logger_(logger),
54      file_name_(file_name),
55      output_handle_(Log::CreateOutputHandle(file_name)),
56      os_(output_handle_ == nullptr ? stdout : output_handle_),
57      format_buffer_(NewArray<char>(kMessageBufferSize)) {
58  if (output_handle_) WriteLogHeader();
59}
60
61void Log::WriteLogHeader() {
62  Log::MessageBuilder msg(this);
63  LogSeparator kNext = LogSeparator::kSeparator;
64  msg << "v8-version" << kNext << Version::GetMajor() << kNext
65      << Version::GetMinor() << kNext << Version::GetBuild() << kNext
66      << Version::GetPatch();
67  if (strlen(Version::GetEmbedder()) != 0) {
68    msg << kNext << Version::GetEmbedder();
69  }
70  msg << kNext << Version::IsCandidate();
71  msg.WriteToLogFile();
72  msg << "v8-platform" << kNext << V8_OS_STRING << kNext << V8_TARGET_OS_STRING;
73  msg.WriteToLogFile();
74}
75
76std::unique_ptr<Log::MessageBuilder> Log::NewMessageBuilder() {
77  // Fast check of is_logging() without taking the lock. Bail out immediately if
78  // logging isn't enabled.
79  if (!logger_->is_logging()) return {};
80
81  std::unique_ptr<Log::MessageBuilder> result(new Log::MessageBuilder(this));
82
83  // The first invocation of is_logging() might still read an old value. It is
84  // fine if a background thread starts logging a bit later, but we want to
85  // avoid background threads continue logging after logging was already closed.
86  if (!logger_->is_logging()) return {};
87  DCHECK_NOT_NULL(format_buffer_.get());
88
89  return result;
90}
91
92FILE* Log::Close() {
93  FILE* result = nullptr;
94  if (output_handle_ != nullptr) {
95    fflush(output_handle_);
96    result = output_handle_;
97  }
98  output_handle_ = nullptr;
99  format_buffer_.reset();
100  return result;
101}
102
103std::string Log::file_name() const { return file_name_; }
104
105Log::MessageBuilder::MessageBuilder(Log* log)
106    : log_(log), lock_guard_(&log_->mutex_) {
107}
108
109void Log::MessageBuilder::AppendString(String str,
110                                       base::Optional<int> length_limit) {
111  if (str.is_null()) return;
112
113  DisallowGarbageCollection no_gc;  // Ensure string stays valid.
114  PtrComprCageBase cage_base = GetPtrComprCageBase(str);
115  SharedStringAccessGuardIfNeeded access_guard(str);
116  int length = str.length();
117  if (length_limit) length = std::min(length, *length_limit);
118  for (int i = 0; i < length; i++) {
119    uint16_t c = str.Get(i, cage_base, access_guard);
120    if (c <= 0xFF) {
121      AppendCharacter(static_cast<char>(c));
122    } else {
123      // Escape non-ascii characters.
124      AppendRawFormatString("\\u%04x", c & 0xFFFF);
125    }
126  }
127}
128
129void Log::MessageBuilder::AppendString(base::Vector<const char> str) {
130  for (auto i = str.begin(); i < str.end(); i++) AppendCharacter(*i);
131}
132
133void Log::MessageBuilder::AppendString(const char* str) {
134  if (str == nullptr) return;
135  AppendString(str, strlen(str));
136}
137
138void Log::MessageBuilder::AppendString(const char* str, size_t length,
139                                       bool is_one_byte) {
140  if (str == nullptr) return;
141  if (is_one_byte) {
142    for (size_t i = 0; i < length; i++) {
143      DCHECK_IMPLIES(is_one_byte, str[i] != '\0');
144      AppendCharacter(str[i]);
145    }
146  } else {
147    DCHECK_EQ(length % 2, 0);
148    for (size_t i = 0; i + 1 < length; i += 2) {
149      AppendTwoByteCharacter(str[i], str[i + 1]);
150    }
151  }
152}
153
154void Log::MessageBuilder::AppendFormatString(const char* format, ...) {
155  va_list args;
156  va_start(args, format);
157  const int length = FormatStringIntoBuffer(format, args);
158  va_end(args);
159  for (int i = 0; i < length; i++) {
160    DCHECK_NE(log_->format_buffer_[i], '\0');
161    AppendCharacter(log_->format_buffer_[i]);
162  }
163}
164
165void Log::MessageBuilder::AppendTwoByteCharacter(char c1, char c2) {
166  if (c2 == 0) {
167    AppendCharacter(c1);
168  } else {
169    // Escape non-printable characters.
170    AppendRawFormatString("\\u%02x%02x", c1 & 0xFF, c2 & 0xFF);
171  }
172}
173void Log::MessageBuilder::AppendCharacter(char c) {
174  if (c >= 32 && c <= 126) {
175    if (c == ',') {
176      // Escape commas to avoid adding column separators.
177      AppendRawFormatString("\\x2C");
178    } else if (c == '\\') {
179      AppendRawFormatString("\\\\");
180    } else {
181      // Safe, printable ascii character.
182      AppendRawCharacter(c);
183    }
184  } else if (c == '\n') {
185    // Escape newlines to avoid adding row separators.
186    AppendRawFormatString("\\n");
187  } else {
188    // Escape non-printable characters.
189    AppendRawFormatString("\\x%02x", c & 0xFF);
190  }
191}
192
193void Log::MessageBuilder::AppendSymbolName(Symbol symbol) {
194  DCHECK(!symbol.is_null());
195  OFStream& os = log_->os_;
196  os << "symbol(";
197  if (!symbol.description().IsUndefined()) {
198    os << "\"";
199    AppendSymbolNameDetails(String::cast(symbol.description()), false);
200    os << "\" ";
201  }
202  os << "hash " << std::hex << symbol.hash() << std::dec << ")";
203}
204
205void Log::MessageBuilder::AppendSymbolNameDetails(String str,
206                                                  bool show_impl_info) {
207  if (str.is_null()) return;
208
209  DisallowGarbageCollection no_gc;  // Ensure string stays valid.
210  OFStream& os = log_->os_;
211  int limit = str.length();
212  if (limit > 0x1000) limit = 0x1000;
213  if (show_impl_info) {
214    os << (str.IsOneByteRepresentation() ? 'a' : '2');
215    if (StringShape(str).IsExternal()) os << 'e';
216    if (StringShape(str).IsInternalized()) os << '#';
217    os << ':' << str.length() << ':';
218  }
219  AppendString(str, limit);
220}
221
222int Log::MessageBuilder::FormatStringIntoBuffer(const char* format,
223                                                va_list args) {
224  base::Vector<char> buf(log_->format_buffer_.get(), Log::kMessageBufferSize);
225  int length = base::VSNPrintF(buf, format, args);
226  // |length| is -1 if output was truncated.
227  if (length == -1) length = Log::kMessageBufferSize;
228  DCHECK_LE(length, Log::kMessageBufferSize);
229  DCHECK_GE(length, 0);
230  return length;
231}
232
233void Log::MessageBuilder::AppendRawFormatString(const char* format, ...) {
234  va_list args;
235  va_start(args, format);
236  const int length = FormatStringIntoBuffer(format, args);
237  va_end(args);
238  for (int i = 0; i < length; i++) {
239    DCHECK_NE(log_->format_buffer_[i], '\0');
240    AppendRawCharacter(log_->format_buffer_[i]);
241  }
242}
243
244void Log::MessageBuilder::AppendRawCharacter(char c) { log_->os_ << c; }
245
246void Log::MessageBuilder::WriteToLogFile() {
247  log_->os_ << std::endl;
248}
249
250template <>
251Log::MessageBuilder& Log::MessageBuilder::operator<<<const char*>(
252    const char* string) {
253  this->AppendString(string);
254  return *this;
255}
256
257template <>
258Log::MessageBuilder& Log::MessageBuilder::operator<<<void*>(void* pointer) {
259  OFStream& os = log_->os_;
260  // Manually format the pointer since on Windows we do not consistently
261  // get a "0x" prefix.
262  os << "0x" << std::hex << reinterpret_cast<intptr_t>(pointer) << std::dec;
263  return *this;
264}
265
266template <>
267Log::MessageBuilder& Log::MessageBuilder::operator<<<char>(char c) {
268  this->AppendCharacter(c);
269  return *this;
270}
271
272template <>
273Log::MessageBuilder& Log::MessageBuilder::operator<<<String>(String string) {
274  this->AppendString(string);
275  return *this;
276}
277
278template <>
279Log::MessageBuilder& Log::MessageBuilder::operator<<<Symbol>(Symbol symbol) {
280  this->AppendSymbolName(symbol);
281  return *this;
282}
283
284template <>
285Log::MessageBuilder& Log::MessageBuilder::operator<<<Name>(Name name) {
286  if (name.IsString()) {
287    this->AppendString(String::cast(name));
288  } else {
289    this->AppendSymbolName(Symbol::cast(name));
290  }
291  return *this;
292}
293
294template <>
295Log::MessageBuilder& Log::MessageBuilder::operator<<<LogSeparator>(
296    LogSeparator separator) {
297  // Skip escaping to create a new column.
298  this->AppendRawCharacter(',');
299  return *this;
300}
301
302}  // namespace internal
303}  // namespace v8
304