mirror of
https://git.suyu.dev/suyu/breakpad.git
synced 2025-12-29 02:35:31 +01:00
Open sourcing the Breakpad framework from Google.
A=many, many people R=nealsid, jeremy moskovich(from Chromium project) git-svn-id: http://google-breakpad.googlecode.com/svn/trunk@322 4c0a9323-5329-0410-9bdc-e9ce6186880e
This commit is contained in:
parent
0abe34ce5d
commit
3ebdb1bd7a
49 changed files with 12695 additions and 22 deletions
241
src/common/mac/GTMDefines.h
Normal file
241
src/common/mac/GTMDefines.h
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
//
|
||||
// GTMDefines.h
|
||||
//
|
||||
// Copyright 2008 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy
|
||||
// of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
//
|
||||
|
||||
// ============================================================================
|
||||
|
||||
#include <AvailabilityMacros.h>
|
||||
#include <TargetConditionals.h>
|
||||
|
||||
// Not all MAC_OS_X_VERSION_10_X macros defined in past SDKs
|
||||
#ifndef MAC_OS_X_VERSION_10_5
|
||||
#define MAC_OS_X_VERSION_10_5 1050
|
||||
#endif
|
||||
#ifndef MAC_OS_X_VERSION_10_6
|
||||
#define MAC_OS_X_VERSION_10_6 1060
|
||||
#endif
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CPP symbols that can be overridden in a prefix to control how the toolbox
|
||||
// is compiled.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
// By setting the GTM_CONTAINERS_VALIDATION_FAILED_LOG and
|
||||
// GTM_CONTAINERS_VALIDATION_FAILED_ASSERT macros you can control what happens
|
||||
// when a validation fails. If you implement your own validators, you may want
|
||||
// to control their internals using the same macros for consistency.
|
||||
#ifndef GTM_CONTAINERS_VALIDATION_FAILED_ASSERT
|
||||
#define GTM_CONTAINERS_VALIDATION_FAILED_ASSERT 0
|
||||
#endif
|
||||
|
||||
// Give ourselves a consistent way to do inlines. Apple's macros even use
|
||||
// a few different actual definitions, so we're based off of the foundation
|
||||
// one.
|
||||
#if !defined(GTM_INLINE)
|
||||
#if defined (__GNUC__) && (__GNUC__ == 4)
|
||||
#define GTM_INLINE static __inline__ __attribute__((always_inline))
|
||||
#else
|
||||
#define GTM_INLINE static __inline__
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Give ourselves a consistent way of doing externs that links up nicely
|
||||
// when mixing objc and objc++
|
||||
#if !defined (GTM_EXTERN)
|
||||
#if defined __cplusplus
|
||||
#define GTM_EXTERN extern "C"
|
||||
#else
|
||||
#define GTM_EXTERN extern
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Give ourselves a consistent way of exporting things if we have visibility
|
||||
// set to hidden.
|
||||
#if !defined (GTM_EXPORT)
|
||||
#define GTM_EXPORT __attribute__((visibility("default")))
|
||||
#endif
|
||||
|
||||
// _GTMDevLog & _GTMDevAssert
|
||||
//
|
||||
// _GTMDevLog & _GTMDevAssert are meant to be a very lightweight shell for
|
||||
// developer level errors. This implementation simply macros to NSLog/NSAssert.
|
||||
// It is not intended to be a general logging/reporting system.
|
||||
//
|
||||
// Please see http://code.google.com/p/google-toolbox-for-mac/wiki/DevLogNAssert
|
||||
// for a little more background on the usage of these macros.
|
||||
//
|
||||
// _GTMDevLog log some error/problem in debug builds
|
||||
// _GTMDevAssert assert if conditon isn't met w/in a method/function
|
||||
// in all builds.
|
||||
//
|
||||
// To replace this system, just provide different macro definitions in your
|
||||
// prefix header. Remember, any implementation you provide *must* be thread
|
||||
// safe since this could be called by anything in what ever situtation it has
|
||||
// been placed in.
|
||||
//
|
||||
|
||||
// We only define the simple macros if nothing else has defined this.
|
||||
#ifndef _GTMDevLog
|
||||
|
||||
#ifdef DEBUG
|
||||
#define _GTMDevLog(...) NSLog(__VA_ARGS__)
|
||||
#else
|
||||
#define _GTMDevLog(...) do { } while (0)
|
||||
#endif
|
||||
|
||||
#endif // _GTMDevLog
|
||||
|
||||
// Declared here so that it can easily be used for logging tracking if
|
||||
// necessary. See GTMUnitTestDevLog.h for details.
|
||||
@class NSString;
|
||||
GTM_EXTERN void _GTMUnitTestDevLog(NSString *format, ...);
|
||||
|
||||
#ifndef _GTMDevAssert
|
||||
// we directly invoke the NSAssert handler so we can pass on the varargs
|
||||
// (NSAssert doesn't have a macro we can use that takes varargs)
|
||||
#if !defined(NS_BLOCK_ASSERTIONS)
|
||||
#define _GTMDevAssert(condition, ...) \
|
||||
do { \
|
||||
if (!(condition)) { \
|
||||
[[NSAssertionHandler currentHandler] \
|
||||
handleFailureInFunction:[NSString stringWithUTF8String:__PRETTY_FUNCTION__] \
|
||||
file:[NSString stringWithUTF8String:__FILE__] \
|
||||
lineNumber:__LINE__ \
|
||||
description:__VA_ARGS__]; \
|
||||
} \
|
||||
} while(0)
|
||||
#else // !defined(NS_BLOCK_ASSERTIONS)
|
||||
#define _GTMDevAssert(condition, ...) do { } while (0)
|
||||
#endif // !defined(NS_BLOCK_ASSERTIONS)
|
||||
|
||||
#endif // _GTMDevAssert
|
||||
|
||||
// _GTMCompileAssert
|
||||
// _GTMCompileAssert is an assert that is meant to fire at compile time if you
|
||||
// want to check things at compile instead of runtime. For example if you
|
||||
// want to check that a wchar is 4 bytes instead of 2 you would use
|
||||
// _GTMCompileAssert(sizeof(wchar_t) == 4, wchar_t_is_4_bytes_on_OS_X)
|
||||
// Note that the second "arg" is not in quotes, and must be a valid processor
|
||||
// symbol in it's own right (no spaces, punctuation etc).
|
||||
|
||||
// Wrapping this in an #ifndef allows external groups to define their own
|
||||
// compile time assert scheme.
|
||||
#ifndef _GTMCompileAssert
|
||||
// We got this technique from here:
|
||||
// http://unixjunkie.blogspot.com/2007/10/better-compile-time-asserts_29.html
|
||||
|
||||
#define _GTMCompileAssertSymbolInner(line, msg) _GTMCOMPILEASSERT ## line ## __ ## msg
|
||||
#define _GTMCompileAssertSymbol(line, msg) _GTMCompileAssertSymbolInner(line, msg)
|
||||
#define _GTMCompileAssert(test, msg) \
|
||||
typedef char _GTMCompileAssertSymbol(__LINE__, msg) [ ((test) ? 1 : -1) ]
|
||||
#endif // _GTMCompileAssert
|
||||
|
||||
// Macro to allow fast enumeration when building for 10.5 or later, and
|
||||
// reliance on NSEnumerator for 10.4. Remember, NSDictionary w/ FastEnumeration
|
||||
// does keys, so pick the right thing, nothing is done on the FastEnumeration
|
||||
// side to be sure you're getting what you wanted.
|
||||
#ifndef GTM_FOREACH_OBJECT
|
||||
#if TARGET_OS_IPHONE || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
|
||||
#define GTM_FOREACH_OBJECT(element, collection) \
|
||||
for (element in collection)
|
||||
#define GTM_FOREACH_KEY(element, collection) \
|
||||
for (element in collection)
|
||||
#else
|
||||
#define GTM_FOREACH_OBJECT(element, collection) \
|
||||
for (NSEnumerator * _ ## element ## _enum = [collection objectEnumerator]; \
|
||||
(element = [_ ## element ## _enum nextObject]) != nil; )
|
||||
#define GTM_FOREACH_KEY(element, collection) \
|
||||
for (NSEnumerator * _ ## element ## _enum = [collection keyEnumerator]; \
|
||||
(element = [_ ## element ## _enum nextObject]) != nil; )
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// ============================================================================
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// CPP symbols defined based on the project settings so the GTM code has
|
||||
// simple things to test against w/o scattering the knowledge of project
|
||||
// setting through all the code.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// Provide a single constant CPP symbol that all of GTM uses for ifdefing
|
||||
// iPhone code.
|
||||
#if TARGET_OS_IPHONE // iPhone SDK
|
||||
// For iPhone specific stuff
|
||||
#define GTM_IPHONE_SDK 1
|
||||
#if TARGET_IPHONE_SIMULATOR
|
||||
#define GTM_IPHONE_SIMULATOR 1
|
||||
#else
|
||||
#define GTM_IPHONE_DEVICE 1
|
||||
#endif // TARGET_IPHONE_SIMULATOR
|
||||
#else
|
||||
// For MacOS specific stuff
|
||||
#define GTM_MACOS_SDK 1
|
||||
#endif
|
||||
|
||||
// Provide a symbol to include/exclude extra code for GC support. (This mainly
|
||||
// just controls the inclusion of finalize methods).
|
||||
#ifndef GTM_SUPPORT_GC
|
||||
#if GTM_IPHONE_SDK
|
||||
// iPhone never needs GC
|
||||
#define GTM_SUPPORT_GC 0
|
||||
#else
|
||||
// We can't find a symbol to tell if GC is supported/required, so best we
|
||||
// do on Mac targets is include it if we're on 10.5 or later.
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
|
||||
#define GTM_SUPPORT_GC 0
|
||||
#else
|
||||
#define GTM_SUPPORT_GC 1
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// To simplify support for 64bit (and Leopard in general), we provide the type
|
||||
// defines for non Leopard SDKs
|
||||
#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
|
||||
// NSInteger/NSUInteger and Max/Mins
|
||||
#ifndef NSINTEGER_DEFINED
|
||||
#if __LP64__ || NS_BUILD_32_LIKE_64
|
||||
typedef long NSInteger;
|
||||
typedef unsigned long NSUInteger;
|
||||
#else
|
||||
typedef int NSInteger;
|
||||
typedef unsigned int NSUInteger;
|
||||
#endif
|
||||
#define NSIntegerMax LONG_MAX
|
||||
#define NSIntegerMin LONG_MIN
|
||||
#define NSUIntegerMax ULONG_MAX
|
||||
#define NSINTEGER_DEFINED 1
|
||||
#endif // NSINTEGER_DEFINED
|
||||
// CGFloat
|
||||
#ifndef CGFLOAT_DEFINED
|
||||
#if defined(__LP64__) && __LP64__
|
||||
// This really is an untested path (64bit on Tiger?)
|
||||
typedef double CGFloat;
|
||||
#define CGFLOAT_MIN DBL_MIN
|
||||
#define CGFLOAT_MAX DBL_MAX
|
||||
#define CGFLOAT_IS_DOUBLE 1
|
||||
#else /* !defined(__LP64__) || !__LP64__ */
|
||||
typedef float CGFloat;
|
||||
#define CGFLOAT_MIN FLT_MIN
|
||||
#define CGFLOAT_MAX FLT_MAX
|
||||
#define CGFLOAT_IS_DOUBLE 0
|
||||
#endif /* !defined(__LP64__) || !__LP64__ */
|
||||
#define CGFLOAT_DEFINED 1
|
||||
#endif // CGFLOAT_DEFINED
|
||||
#endif // MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_4
|
||||
72
src/common/mac/GTMGarbageCollection.h
Normal file
72
src/common/mac/GTMGarbageCollection.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// GTMGarbageCollection.h
|
||||
//
|
||||
// Copyright 2007-2008 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy
|
||||
// of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "GTMDefines.h"
|
||||
|
||||
// This allows us to easily move our code from GC to non GC.
|
||||
// They are no-ops unless we are require Leopard or above.
|
||||
// See
|
||||
// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/index.html
|
||||
// and
|
||||
// http://developer.apple.com/documentation/Cocoa/Conceptual/GarbageCollection/Articles/gcCoreFoundation.html#//apple_ref/doc/uid/TP40006687-SW1
|
||||
// for details.
|
||||
|
||||
#if (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5) && !GTM_IPHONE_SDK
|
||||
// General use would be to call this through GTMCFAutorelease
|
||||
// but there may be a reason the you want to make something collectable
|
||||
// but not autoreleased, especially in pure GC code where you don't
|
||||
// want to bother with the nop autorelease. Done as a define instead of an
|
||||
// inline so that tools like Clang's scan-build don't report code as leaking.
|
||||
#define GTMNSMakeCollectable(cf) ((id)NSMakeCollectable(cf))
|
||||
|
||||
// GTMNSMakeUncollectable is for global maps, etc. that we don't
|
||||
// want released ever. You should still retain these in non-gc code.
|
||||
GTM_INLINE void GTMNSMakeUncollectable(id object) {
|
||||
[[NSGarbageCollector defaultCollector] disableCollectorForPointer:object];
|
||||
}
|
||||
|
||||
// Hopefully no code really needs this, but GTMIsGarbageCollectionEnabled is
|
||||
// a common way to check at runtime if GC is on.
|
||||
// There are some places where GC doesn't work w/ things w/in Apple's
|
||||
// frameworks, so this is here so GTM unittests and detect it, and not run
|
||||
// individual tests to work around bugs in Apple's frameworks.
|
||||
GTM_INLINE BOOL GTMIsGarbageCollectionEnabled(void) {
|
||||
return ([NSGarbageCollector defaultCollector] != nil);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define GTMNSMakeCollectable(cf) ((id)(cf))
|
||||
|
||||
GTM_INLINE void GTMNSMakeUncollectable(id object) {
|
||||
}
|
||||
|
||||
GTM_INLINE BOOL GTMIsGarbageCollectionEnabled(void) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// GTMCFAutorelease makes a CF object collectable in GC mode, or adds it
|
||||
// to the autorelease pool in non-GC mode. Either way it is taken care
|
||||
// of. Done as a define instead of an inline so that tools like Clang's
|
||||
// scan-build don't report code as leaking.
|
||||
#define GTMCFAutorelease(cf) ([GTMNSMakeCollectable(cf) autorelease])
|
||||
|
||||
458
src/common/mac/GTMLogger.h
Normal file
458
src/common/mac/GTMLogger.h
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
//
|
||||
// GTMLogger.h
|
||||
//
|
||||
// Copyright 2007-2008 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy
|
||||
// of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
//
|
||||
|
||||
// Key Abstractions
|
||||
// ----------------
|
||||
//
|
||||
// This file declares multiple classes and protocols that are used by the
|
||||
// GTMLogger logging system. The 4 main abstractions used in this file are the
|
||||
// following:
|
||||
//
|
||||
// * logger (GTMLogger) - The main logging class that users interact with. It
|
||||
// has methods for logging at different levels and uses a log writer, a log
|
||||
// formatter, and a log filter to get the job done.
|
||||
//
|
||||
// * log writer (GTMLogWriter) - Writes a given string to some log file, where
|
||||
// a "log file" can be a physical file on disk, a POST over HTTP to some URL,
|
||||
// or even some in-memory structure (e.g., a ring buffer).
|
||||
//
|
||||
// * log formatter (GTMLogFormatter) - Given a format string and arguments as
|
||||
// a va_list, returns a single formatted NSString. A "formatted string" could
|
||||
// be a string with the date prepended, a string with values in a CSV format,
|
||||
// or even a string of XML.
|
||||
//
|
||||
// * log filter (GTMLogFilter) - Given a formatted log message as an NSString
|
||||
// and the level at which the message is to be logged, this class will decide
|
||||
// whether the given message should be logged or not. This is a flexible way
|
||||
// to filter out messages logged at a certain level, messages that contain
|
||||
// certain text, or filter nothing out at all. This gives the caller the
|
||||
// flexibility to dynamically enable debug logging in Release builds.
|
||||
//
|
||||
// This file also declares some classes to handle the common log writer, log
|
||||
// formatter, and log filter cases. Callers can also create their own writers,
|
||||
// formatters, and filters and they can even build them on top of the ones
|
||||
// declared here. Keep in mind that your custom writer/formatter/filter may be
|
||||
// called from multiple threads, so it must be thread-safe.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
// Predeclaration of used protocols that are declared later in this file.
|
||||
@protocol GTMLogWriter, GTMLogFormatter, GTMLogFilter;
|
||||
|
||||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5
|
||||
#define CHECK_FORMAT_NSSTRING(a, b) __attribute__((format(__NSString__, a, b)))
|
||||
#else
|
||||
#define CHECK_FORMAT_NSSTRING(a, b)
|
||||
#endif
|
||||
|
||||
// GTMLogger
|
||||
//
|
||||
// GTMLogger is the primary user-facing class for an object-oriented logging
|
||||
// system. It is built on the concept of log formatters (GTMLogFormatter), log
|
||||
// writers (GTMLogWriter), and log filters (GTMLogFilter). When a message is
|
||||
// sent to a GTMLogger to log a message, the message is formatted using the log
|
||||
// formatter, then the log filter is consulted to see if the message should be
|
||||
// logged, and if so, the message is sent to the log writer to be written out.
|
||||
//
|
||||
// GTMLogger is intended to be a flexible and thread-safe logging solution. Its
|
||||
// flexibility comes from the fact that GTMLogger instances can be customized
|
||||
// with user defined formatters, filters, and writers. And these writers,
|
||||
// filters, and formatters can be combined, stacked, and customized in arbitrary
|
||||
// ways to suit the needs at hand. For example, multiple writers can be used at
|
||||
// the same time, and a GTMLogger instance can even be used as another
|
||||
// GTMLogger's writer. This allows for arbitrarily deep logging trees.
|
||||
//
|
||||
// A standard GTMLogger uses a writer that sends messages to standard out, a
|
||||
// formatter that smacks a timestamp and a few other bits of interesting
|
||||
// information on the message, and a filter that filters out debug messages from
|
||||
// release builds. Using the standard log settings, a log message will look like
|
||||
// the following:
|
||||
//
|
||||
// 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] foo=<Foo: 0x123>
|
||||
//
|
||||
// The output contains the date and time of the log message, the name of the
|
||||
// process followed by its process ID/thread ID, the log level at which the
|
||||
// message was logged (in the previous example the level was 1:
|
||||
// kGTMLoggerLevelDebug), and finally, the user-specified log message itself (in
|
||||
// this case, the log message was @"foo=%@", foo).
|
||||
//
|
||||
// Multiple instances of GTMLogger can be created, each configured their own
|
||||
// way. Though GTMLogger is not a singleton (in the GoF sense), it does provide
|
||||
// access to a shared (i.e., globally accessible) GTMLogger instance. This makes
|
||||
// it convenient for all code in a process to use the same GTMLogger instance.
|
||||
// The shared GTMLogger instance can also be configured in an arbitrary, and
|
||||
// these configuration changes will affect all code that logs through the shared
|
||||
// instance.
|
||||
|
||||
//
|
||||
// Log Levels
|
||||
// ----------
|
||||
// GTMLogger has 3 different log levels: Debug, Info, and Error. GTMLogger
|
||||
// doesn't take any special action based on the log level; it simply forwards
|
||||
// this information on to formatters, filters, and writers, each of which may
|
||||
// optionally take action based on the level. Since log level filtering is
|
||||
// performed at runtime, log messages are typically not filtered out at compile
|
||||
// time. The exception to this rule is that calls to the GTMLoggerDebug() macro
|
||||
// *ARE* filtered out of non-DEBUG builds. This is to be backwards compatible
|
||||
// with behavior that many developers are currently used to. Note that this
|
||||
// means that GTMLoggerDebug(@"hi") will be compiled out of Release builds, but
|
||||
// [[GTMLogger sharedLogger] logDebug:@"hi"] will NOT be compiled out.
|
||||
//
|
||||
// Standard loggers are created with the GTMLogLevelFilter log filter, which
|
||||
// filters out certain log messages based on log level, and some other settings.
|
||||
//
|
||||
// In addition to the -logDebug:, -logInfo:, and -logError: methods defined on
|
||||
// GTMLogger itself, there are also C macros that make usage of the shared
|
||||
// GTMLogger instance very convenient. These macros are:
|
||||
//
|
||||
// GTMLoggerDebug(...)
|
||||
// GTMLoggerInfo(...)
|
||||
// GTMLoggerError(...)
|
||||
//
|
||||
// Again, a notable feature of these macros is that GTMLogDebug() calls *will be
|
||||
// compiled out of non-DEBUG builds*.
|
||||
//
|
||||
// Standard Loggers
|
||||
// ----------------
|
||||
// GTMLogger has the concept of "standard loggers". A standard logger is simply
|
||||
// a logger that is pre-configured with some standard/common writer, formatter,
|
||||
// and filter combination. Standard loggers are created using the creation
|
||||
// methods beginning with "standard". The alternative to a standard logger is a
|
||||
// regular logger, which will send messages to stdout, with no special
|
||||
// formatting, and no filtering.
|
||||
//
|
||||
// How do I use GTMLogger?
|
||||
// ----------------------
|
||||
// The typical way you will want to use GTMLogger is to simply use the
|
||||
// GTMLogger*() macros for logging from code. That way we can easily make
|
||||
// changes to the GTMLogger class and simply update the macros accordingly. Only
|
||||
// your application startup code (perhaps, somewhere in main()) should use the
|
||||
// GTMLogger class directly in order to configure the shared logger, which all
|
||||
// of the code using the macros will be using. Again, this is just the typical
|
||||
// situation.
|
||||
//
|
||||
// To be complete, there are cases where you may want to use GTMLogger directly,
|
||||
// or even create separate GTMLogger instances for some reason. That's fine,
|
||||
// too.
|
||||
//
|
||||
// Examples
|
||||
// --------
|
||||
// The following show some common GTMLogger use cases.
|
||||
//
|
||||
// 1. You want to log something as simply as possible. Also, this call will only
|
||||
// appear in debug builds. In non-DEBUG builds it will be completely removed.
|
||||
//
|
||||
// GTMLoggerDebug(@"foo = %@", foo);
|
||||
//
|
||||
// 2. The previous example is similar to the following. The major difference is
|
||||
// that the previous call (example 1) will be compiled out of Release builds
|
||||
// but this statement will not be compiled out.
|
||||
//
|
||||
// [[GTMLogger sharedLogger] logDebug:@"foo = %@", foo];
|
||||
//
|
||||
// 3. Send all logging output from the shared logger to a file. We do this by
|
||||
// creating an NSFileHandle for writing associated with a file, and setting
|
||||
// that file handle as the logger's writer.
|
||||
//
|
||||
// NSFileHandle *f = [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log"
|
||||
// create:YES];
|
||||
// [[GTMLogger sharedLogger] setWriter:f];
|
||||
// GTMLoggerError(@"hi"); // This will be sent to /tmp/f.log
|
||||
//
|
||||
// 4. Create a new GTMLogger that will log to a file. This example differs from
|
||||
// the previous one because here we create a new GTMLogger that is different
|
||||
// from the shared logger.
|
||||
//
|
||||
// GTMLogger *logger = [GTMLogger standardLoggerWithPath:@"/tmp/temp.log"];
|
||||
// [logger logInfo:@"hi temp log file"];
|
||||
//
|
||||
// 5. Create a logger that writes to stdout and does NOT do any formatting to
|
||||
// the log message. This might be useful, for example, when writing a help
|
||||
// screen for a command-line tool to standard output.
|
||||
//
|
||||
// GTMLogger *logger = [GTMLogger logger];
|
||||
// [logger logInfo:@"%@ version 0.1 usage", progName];
|
||||
//
|
||||
// 6. Send log output to stdout AND to a log file. The trick here is that
|
||||
// NSArrays function as composite log writers, which means when an array is
|
||||
// set as the log writer, it forwards all logging messages to all of its
|
||||
// contained GTMLogWriters.
|
||||
//
|
||||
// // Create array of GTMLogWriters
|
||||
// NSArray *writers = [NSArray arrayWithObjects:
|
||||
// [NSFileHandle fileHandleForWritingAtPath:@"/tmp/f.log" create:YES],
|
||||
// [NSFileHandle fileHandleWithStandardOutput], nil];
|
||||
//
|
||||
// GTMLogger *logger = [GTMLogger standardLogger];
|
||||
// [logger setWriter:writers];
|
||||
// [logger logInfo:@"hi"]; // Output goes to stdout and /tmp/f.log
|
||||
//
|
||||
// For futher details on log writers, formatters, and filters, see the
|
||||
// documentation below.
|
||||
//
|
||||
// NOTE: GTMLogger is application level logging. By default it does nothing
|
||||
// with _GTMDevLog/_GTMDevAssert (see GTMDefines.h). An application can choose
|
||||
// to bridge _GTMDevLog/_GTMDevAssert to GTMLogger by providing macro
|
||||
// definitions in its prefix header (see GTMDefines.h for how one would do
|
||||
// that).
|
||||
//
|
||||
@interface GTMLogger : NSObject {
|
||||
@private
|
||||
id<GTMLogWriter> writer_;
|
||||
id<GTMLogFormatter> formatter_;
|
||||
id<GTMLogFilter> filter_;
|
||||
}
|
||||
|
||||
//
|
||||
// Accessors for the shared logger instance
|
||||
//
|
||||
|
||||
// Returns a shared/global standard GTMLogger instance. Callers should typically
|
||||
// use this method to get a GTMLogger instance, unless they explicitly want
|
||||
// their own instance to configure for their own needs. This is the only method
|
||||
// that returns a shared instance; all the rest return new GTMLogger instances.
|
||||
+ (id)sharedLogger;
|
||||
|
||||
// Sets the shared logger instance to |logger|. Future calls to +sharedLogger
|
||||
// will return |logger| instead.
|
||||
+ (void)setSharedLogger:(GTMLogger *)logger;
|
||||
|
||||
//
|
||||
// Creation methods
|
||||
//
|
||||
|
||||
// Returns a new autoreleased GTMLogger instance that will log to stdout, using
|
||||
// the GTMLogStandardFormatter, and the GTMLogLevelFilter filter.
|
||||
+ (id)standardLogger;
|
||||
|
||||
// Same as +standardLogger, but logs to stderr.
|
||||
+ (id)standardLoggerWithStderr;
|
||||
|
||||
// Returns a new standard GTMLogger instance with a log writer that will
|
||||
// write to the file at |path|, and will use the GTMLogStandardFormatter and
|
||||
// GTMLogLevelFilter classes. If |path| does not exist, it will be created.
|
||||
+ (id)standardLoggerWithPath:(NSString *)path;
|
||||
|
||||
// Returns an autoreleased GTMLogger instance that will use the specified
|
||||
// |writer|, |formatter|, and |filter|.
|
||||
+ (id)loggerWithWriter:(id<GTMLogWriter>)writer
|
||||
formatter:(id<GTMLogFormatter>)formatter
|
||||
filter:(id<GTMLogFilter>)filter;
|
||||
|
||||
// Returns an autoreleased GTMLogger instance that logs to stdout, with the
|
||||
// basic formatter, and no filter. The returned logger differs from the logger
|
||||
// returned by +standardLogger because this one does not do any filtering and
|
||||
// does not do any special log formatting; this is the difference between a
|
||||
// "regular" logger and a "standard" logger.
|
||||
+ (id)logger;
|
||||
|
||||
// Designated initializer. This method returns a GTMLogger initialized with the
|
||||
// specified |writer|, |formatter|, and |filter|. See the setter methods below
|
||||
// for what values will be used if nil is passed for a parameter.
|
||||
- (id)initWithWriter:(id<GTMLogWriter>)writer
|
||||
formatter:(id<GTMLogFormatter>)formatter
|
||||
filter:(id<GTMLogFilter>)filter;
|
||||
|
||||
//
|
||||
// Logging methods
|
||||
//
|
||||
|
||||
// Logs a message at the debug level (kGTMLoggerLevelDebug).
|
||||
- (void)logDebug:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||
// Logs a message at the info level (kGTMLoggerLevelInfo).
|
||||
- (void)logInfo:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||
// Logs a message at the error level (kGTMLoggerLevelError).
|
||||
- (void)logError:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||
// Logs a message at the assert level (kGTMLoggerLevelAssert).
|
||||
- (void)logAssert:(NSString *)fmt, ... CHECK_FORMAT_NSSTRING(1, 2);
|
||||
|
||||
|
||||
//
|
||||
// Accessors
|
||||
//
|
||||
|
||||
// Accessor methods for the log writer. If the log writer is set to nil,
|
||||
// [NSFileHandle fileHandleWithStandardOutput] is used.
|
||||
- (id<GTMLogWriter>)writer;
|
||||
- (void)setWriter:(id<GTMLogWriter>)writer;
|
||||
|
||||
// Accessor methods for the log formatter. If the log formatter is set to nil,
|
||||
// GTMLogBasicFormatter is used. This formatter will format log messages in a
|
||||
// plain printf style.
|
||||
- (id<GTMLogFormatter>)formatter;
|
||||
- (void)setFormatter:(id<GTMLogFormatter>)formatter;
|
||||
|
||||
// Accessor methods for the log filter. If the log filter is set to nil,
|
||||
// GTMLogNoFilter is used, which allows all log messages through.
|
||||
- (id<GTMLogFilter>)filter;
|
||||
- (void)setFilter:(id<GTMLogFilter>)filter;
|
||||
|
||||
@end // GTMLogger
|
||||
|
||||
|
||||
// Helper functions that are used by the convenience GTMLogger*() macros that
|
||||
// enable the logging of function names.
|
||||
@interface GTMLogger (GTMLoggerMacroHelpers)
|
||||
- (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ...
|
||||
CHECK_FORMAT_NSSTRING(2, 3);
|
||||
- (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ...
|
||||
CHECK_FORMAT_NSSTRING(2, 3);
|
||||
- (void)logFuncError:(const char *)func msg:(NSString *)fmt, ...
|
||||
CHECK_FORMAT_NSSTRING(2, 3);
|
||||
- (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ...
|
||||
CHECK_FORMAT_NSSTRING(2, 3);
|
||||
@end // GTMLoggerMacroHelpers
|
||||
|
||||
|
||||
// Convenience macros that log to the shared GTMLogger instance. These macros
|
||||
// are how users should typically log to GTMLogger. Notice that GTMLoggerDebug()
|
||||
// calls will be compiled out of non-Debug builds.
|
||||
#define GTMLoggerDebug(...) \
|
||||
[[GTMLogger sharedLogger] logFuncDebug:__func__ msg:__VA_ARGS__]
|
||||
#define GTMLoggerInfo(...) \
|
||||
[[GTMLogger sharedLogger] logFuncInfo:__func__ msg:__VA_ARGS__]
|
||||
#define GTMLoggerError(...) \
|
||||
[[GTMLogger sharedLogger] logFuncError:__func__ msg:__VA_ARGS__]
|
||||
#define GTMLoggerAssert(...) \
|
||||
[[GTMLogger sharedLogger] logFuncAssert:__func__ msg:__VA_ARGS__]
|
||||
|
||||
// If we're not in a debug build, remove the GTMLoggerDebug statements. This
|
||||
// makes calls to GTMLoggerDebug "compile out" of Release builds
|
||||
#ifndef DEBUG
|
||||
#undef GTMLoggerDebug
|
||||
#define GTMLoggerDebug(...) do {} while(0)
|
||||
#endif
|
||||
|
||||
// Log levels.
|
||||
typedef enum {
|
||||
kGTMLoggerLevelUnknown,
|
||||
kGTMLoggerLevelDebug,
|
||||
kGTMLoggerLevelInfo,
|
||||
kGTMLoggerLevelError,
|
||||
kGTMLoggerLevelAssert,
|
||||
} GTMLoggerLevel;
|
||||
|
||||
|
||||
//
|
||||
// Log Writers
|
||||
//
|
||||
|
||||
// Protocol to be implemented by a GTMLogWriter instance.
|
||||
@protocol GTMLogWriter <NSObject>
|
||||
// Writes the given log message to where the log writer is configured to write.
|
||||
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level;
|
||||
@end // GTMLogWriter
|
||||
|
||||
|
||||
// Simple category on NSFileHandle that makes NSFileHandles valid log writers.
|
||||
// This is convenient because something like, say, +fileHandleWithStandardError
|
||||
// now becomes a valid log writer. Log messages are written to the file handle
|
||||
// with a newline appended.
|
||||
@interface NSFileHandle (GTMFileHandleLogWriter) <GTMLogWriter>
|
||||
// Opens the file at |path| in append mode, and creates the file with |mode|
|
||||
// if it didn't previously exist.
|
||||
+ (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode;
|
||||
@end // NSFileHandle
|
||||
|
||||
|
||||
// This category makes NSArray a GTMLogWriter that can be composed of other
|
||||
// GTMLogWriters. This is the classic Composite GoF design pattern. When the
|
||||
// GTMLogWriter -logMessage:level: message is sent to the array, the array
|
||||
// forwards the message to all of its elements that implement the GTMLogWriter
|
||||
// protocol.
|
||||
//
|
||||
// This is useful in situations where you would like to send log output to
|
||||
// multiple log writers at the same time. Simply create an NSArray of the log
|
||||
// writers you wish to use, then set the array as the "writer" for your
|
||||
// GTMLogger instance.
|
||||
@interface NSArray (GTMArrayCompositeLogWriter) <GTMLogWriter>
|
||||
@end // GTMArrayCompositeLogWriter
|
||||
|
||||
|
||||
// This category adapts the GTMLogger interface so that it can be used as a log
|
||||
// writer; it's an "adapter" in the GoF Adapter pattern sense.
|
||||
//
|
||||
// This is useful when you want to configure a logger to log to a specific
|
||||
// writer with a specific formatter and/or filter. But you want to also compose
|
||||
// that with a different log writer that may have its own formatter and/or
|
||||
// filter.
|
||||
@interface GTMLogger (GTMLoggerLogWriter) <GTMLogWriter>
|
||||
@end // GTMLoggerLogWriter
|
||||
|
||||
|
||||
//
|
||||
// Log Formatters
|
||||
//
|
||||
|
||||
// Protocol to be implemented by a GTMLogFormatter instance.
|
||||
@protocol GTMLogFormatter <NSObject>
|
||||
// Returns a formatted string using the format specified in |fmt| and the va
|
||||
// args specified in |args|.
|
||||
- (NSString *)stringForFunc:(NSString *)func
|
||||
withFormat:(NSString *)fmt
|
||||
valist:(va_list)args
|
||||
level:(GTMLoggerLevel)level;
|
||||
@end // GTMLogFormatter
|
||||
|
||||
|
||||
// A basic log formatter that formats a string the same way that NSLog (or
|
||||
// printf) would. It does not do anything fancy, nor does it add any data of its
|
||||
// own.
|
||||
@interface GTMLogBasicFormatter : NSObject <GTMLogFormatter>
|
||||
@end // GTMLogBasicFormatter
|
||||
|
||||
|
||||
// A log formatter that formats the log string like the basic formatter, but
|
||||
// also prepends a timestamp and some basic process info to the message, as
|
||||
// shown in the following sample output.
|
||||
// 2007-12-30 10:29:24.177 myapp[4588/0xa07d0f60] [lvl=1] log mesage here
|
||||
@interface GTMLogStandardFormatter : GTMLogBasicFormatter {
|
||||
@private
|
||||
NSDateFormatter *dateFormatter_; // yyyy-MM-dd HH:mm:ss.SSS
|
||||
NSString *pname_;
|
||||
pid_t pid_;
|
||||
}
|
||||
@end // GTMLogStandardFormatter
|
||||
|
||||
|
||||
//
|
||||
// Log Filters
|
||||
//
|
||||
|
||||
// Protocol to be imlemented by a GTMLogFilter instance.
|
||||
@protocol GTMLogFilter <NSObject>
|
||||
// Returns YES if |msg| at |level| should be filtered out; NO otherwise.
|
||||
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level;
|
||||
@end // GTMLogFilter
|
||||
|
||||
|
||||
// A log filter that filters messages at the kGTMLoggerLevelDebug level out of
|
||||
// non-debug builds. Messages at the kGTMLoggerLevelInfo level are also filtered
|
||||
// out of non-debug builds unless GTMVerboseLogging is set in the environment or
|
||||
// the processes's defaults. Messages at the kGTMLoggerLevelError level are
|
||||
// never filtered.
|
||||
@interface GTMLogLevelFilter : NSObject <GTMLogFilter>
|
||||
@end // GTMLogLevelFilter
|
||||
|
||||
|
||||
// A simple log filter that does NOT filter anything out;
|
||||
// -filterAllowsMessage:level will always return YES. This can be a convenient
|
||||
// way to enable debug-level logging in release builds (if you so desire).
|
||||
@interface GTMLogNoFilter : NSObject <GTMLogFilter>
|
||||
@end // GTMLogNoFilter
|
||||
|
||||
445
src/common/mac/GTMLogger.m
Normal file
445
src/common/mac/GTMLogger.m
Normal file
|
|
@ -0,0 +1,445 @@
|
|||
//
|
||||
// GTMLogger.m
|
||||
//
|
||||
// Copyright 2007-2008 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy
|
||||
// of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
//
|
||||
|
||||
#import "GTMLogger.h"
|
||||
#import "GTMGarbageCollection.h"
|
||||
#import <fcntl.h>
|
||||
#import <unistd.h>
|
||||
#import <stdlib.h>
|
||||
#import <pthread.h>
|
||||
|
||||
|
||||
// Define a trivial assertion macro to avoid dependencies
|
||||
#ifdef DEBUG
|
||||
#define GTMLOGGER_ASSERT(expr) assert(expr)
|
||||
#else
|
||||
#define GTMLOGGER_ASSERT(expr)
|
||||
#endif
|
||||
|
||||
|
||||
@interface GTMLogger (PrivateMethods)
|
||||
|
||||
- (void)logInternalFunc:(const char *)func
|
||||
format:(NSString *)fmt
|
||||
valist:(va_list)args
|
||||
level:(GTMLoggerLevel)level;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
// Reference to the shared GTMLogger instance. This is not a singleton, it's
|
||||
// just an easy reference to one shared instance.
|
||||
static GTMLogger *gSharedLogger = nil;
|
||||
|
||||
|
||||
@implementation GTMLogger
|
||||
|
||||
// Returns a pointer to the shared logger instance. If none exists, a standard
|
||||
// logger is created and returned.
|
||||
+ (id)sharedLogger {
|
||||
@synchronized(self) {
|
||||
if (gSharedLogger == nil) {
|
||||
gSharedLogger = [[self standardLogger] retain];
|
||||
}
|
||||
GTMLOGGER_ASSERT(gSharedLogger != nil);
|
||||
}
|
||||
return [[gSharedLogger retain] autorelease];
|
||||
}
|
||||
|
||||
+ (void)setSharedLogger:(GTMLogger *)logger {
|
||||
@synchronized(self) {
|
||||
[gSharedLogger autorelease];
|
||||
gSharedLogger = [logger retain];
|
||||
}
|
||||
}
|
||||
|
||||
+ (id)standardLogger {
|
||||
id<GTMLogWriter> writer = [NSFileHandle fileHandleWithStandardOutput];
|
||||
id<GTMLogFormatter> fr = [[[GTMLogStandardFormatter alloc] init] autorelease];
|
||||
id<GTMLogFilter> filter = [[[GTMLogLevelFilter alloc] init] autorelease];
|
||||
return [self loggerWithWriter:writer formatter:fr filter:filter];
|
||||
}
|
||||
|
||||
+ (id)standardLoggerWithStderr {
|
||||
id me = [self standardLogger];
|
||||
[me setWriter:[NSFileHandle fileHandleWithStandardError]];
|
||||
return me;
|
||||
}
|
||||
|
||||
+ (id)standardLoggerWithPath:(NSString *)path {
|
||||
NSFileHandle *fh = [NSFileHandle fileHandleForLoggingAtPath:path mode:0644];
|
||||
if (fh == nil) return nil;
|
||||
id me = [self standardLogger];
|
||||
[me setWriter:fh];
|
||||
return me;
|
||||
}
|
||||
|
||||
+ (id)loggerWithWriter:(id<GTMLogWriter>)writer
|
||||
formatter:(id<GTMLogFormatter>)formatter
|
||||
filter:(id<GTMLogFilter>)filter {
|
||||
return [[[self alloc] initWithWriter:writer
|
||||
formatter:formatter
|
||||
filter:filter] autorelease];
|
||||
}
|
||||
|
||||
+ (id)logger {
|
||||
return [[[self alloc] init] autorelease];
|
||||
}
|
||||
|
||||
- (id)init {
|
||||
return [self initWithWriter:nil formatter:nil filter:nil];
|
||||
}
|
||||
|
||||
- (id)initWithWriter:(id<GTMLogWriter>)writer
|
||||
formatter:(id<GTMLogFormatter>)formatter
|
||||
filter:(id<GTMLogFilter>)filter {
|
||||
if ((self = [super init])) {
|
||||
[self setWriter:writer];
|
||||
[self setFormatter:formatter];
|
||||
[self setFilter:filter];
|
||||
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||
GTMLOGGER_ASSERT(filter_ != nil);
|
||||
GTMLOGGER_ASSERT(writer_ != nil);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
GTMLOGGER_ASSERT(writer_ != nil);
|
||||
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||
GTMLOGGER_ASSERT(filter_ != nil);
|
||||
[writer_ release];
|
||||
[formatter_ release];
|
||||
[filter_ release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (id<GTMLogWriter>)writer {
|
||||
GTMLOGGER_ASSERT(writer_ != nil);
|
||||
return [[writer_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setWriter:(id<GTMLogWriter>)writer {
|
||||
@synchronized(self) {
|
||||
[writer_ autorelease];
|
||||
if (writer == nil)
|
||||
writer_ = [[NSFileHandle fileHandleWithStandardOutput] retain];
|
||||
else
|
||||
writer_ = [writer retain];
|
||||
}
|
||||
GTMLOGGER_ASSERT(writer_ != nil);
|
||||
}
|
||||
|
||||
- (id<GTMLogFormatter>)formatter {
|
||||
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||
return [[formatter_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setFormatter:(id<GTMLogFormatter>)formatter {
|
||||
@synchronized(self) {
|
||||
[formatter_ autorelease];
|
||||
if (formatter == nil)
|
||||
formatter_ = [[GTMLogBasicFormatter alloc] init];
|
||||
else
|
||||
formatter_ = [formatter retain];
|
||||
}
|
||||
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||
}
|
||||
|
||||
- (id<GTMLogFilter>)filter {
|
||||
GTMLOGGER_ASSERT(filter_ != nil);
|
||||
return [[filter_ retain] autorelease];
|
||||
}
|
||||
|
||||
- (void)setFilter:(id<GTMLogFilter>)filter {
|
||||
@synchronized(self) {
|
||||
[filter_ autorelease];
|
||||
if (filter == nil)
|
||||
filter_ = [[GTMLogNoFilter alloc] init];
|
||||
else
|
||||
filter_ = [filter retain];
|
||||
}
|
||||
GTMLOGGER_ASSERT(filter_ != nil);
|
||||
}
|
||||
|
||||
- (void)logDebug:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelDebug];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logInfo:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelInfo];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logError:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelError];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logAssert:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:NULL format:fmt valist:args level:kGTMLoggerLevelAssert];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@end // GTMLogger
|
||||
|
||||
|
||||
@implementation GTMLogger (GTMLoggerMacroHelpers)
|
||||
|
||||
- (void)logFuncDebug:(const char *)func msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelDebug];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logFuncInfo:(const char *)func msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelInfo];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logFuncError:(const char *)func msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelError];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
- (void)logFuncAssert:(const char *)func msg:(NSString *)fmt, ... {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
[self logInternalFunc:func format:fmt valist:args level:kGTMLoggerLevelAssert];
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
@end // GTMLoggerMacroHelpers
|
||||
|
||||
|
||||
@implementation GTMLogger (PrivateMethods)
|
||||
|
||||
- (void)logInternalFunc:(const char *)func
|
||||
format:(NSString *)fmt
|
||||
valist:(va_list)args
|
||||
level:(GTMLoggerLevel)level {
|
||||
GTMLOGGER_ASSERT(formatter_ != nil);
|
||||
GTMLOGGER_ASSERT(filter_ != nil);
|
||||
GTMLOGGER_ASSERT(writer_ != nil);
|
||||
|
||||
NSString *fname = func ? [NSString stringWithUTF8String:func] : nil;
|
||||
NSString *msg = [formatter_ stringForFunc:fname
|
||||
withFormat:fmt
|
||||
valist:args
|
||||
level:level];
|
||||
if (msg && [filter_ filterAllowsMessage:msg level:level])
|
||||
[writer_ logMessage:msg level:level];
|
||||
}
|
||||
|
||||
@end // PrivateMethods
|
||||
|
||||
|
||||
@implementation NSFileHandle (GTMFileHandleLogWriter)
|
||||
|
||||
+ (id)fileHandleForLoggingAtPath:(NSString *)path mode:(mode_t)mode {
|
||||
int fd = -1;
|
||||
if (path) {
|
||||
int flags = O_WRONLY | O_APPEND | O_CREAT;
|
||||
fd = open([path fileSystemRepresentation], flags, mode);
|
||||
}
|
||||
if (fd == -1) return nil;
|
||||
return [[[self alloc] initWithFileDescriptor:fd
|
||||
closeOnDealloc:YES] autorelease];
|
||||
}
|
||||
|
||||
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||
@synchronized(self) {
|
||||
NSString *line = [NSString stringWithFormat:@"%@\n", msg];
|
||||
[self writeData:[line dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
}
|
||||
}
|
||||
|
||||
@end // GTMFileHandleLogWriter
|
||||
|
||||
|
||||
@implementation NSArray (GTMArrayCompositeLogWriter)
|
||||
|
||||
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||
@synchronized(self) {
|
||||
id<GTMLogWriter> child = nil;
|
||||
GTM_FOREACH_OBJECT(child, self) {
|
||||
if ([child conformsToProtocol:@protocol(GTMLogWriter)])
|
||||
[child logMessage:msg level:level];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@end // GTMArrayCompositeLogWriter
|
||||
|
||||
|
||||
@implementation GTMLogger (GTMLoggerLogWriter)
|
||||
|
||||
- (void)logMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||
switch (level) {
|
||||
case kGTMLoggerLevelDebug:
|
||||
[self logDebug:@"%@", msg];
|
||||
break;
|
||||
case kGTMLoggerLevelInfo:
|
||||
[self logInfo:@"%@", msg];
|
||||
break;
|
||||
case kGTMLoggerLevelError:
|
||||
[self logError:@"%@", msg];
|
||||
break;
|
||||
case kGTMLoggerLevelAssert:
|
||||
[self logAssert:@"%@", msg];
|
||||
break;
|
||||
default:
|
||||
// Ignore the message.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@end // GTMLoggerLogWriter
|
||||
|
||||
|
||||
@implementation GTMLogBasicFormatter
|
||||
|
||||
- (NSString *)stringForFunc:(NSString *)func
|
||||
withFormat:(NSString *)fmt
|
||||
valist:(va_list)args
|
||||
level:(GTMLoggerLevel)level {
|
||||
// Performance note: since we always have to create a new NSString from the
|
||||
// returned CFStringRef, we may want to do a quick check here to see if |fmt|
|
||||
// contains a '%', and if not, simply return 'fmt'.
|
||||
CFStringRef cfmsg = NULL;
|
||||
cfmsg = CFStringCreateWithFormatAndArguments(kCFAllocatorDefault,
|
||||
NULL, // format options
|
||||
(CFStringRef)fmt,
|
||||
args);
|
||||
return GTMCFAutorelease(cfmsg);
|
||||
}
|
||||
|
||||
@end // GTMLogBasicFormatter
|
||||
|
||||
|
||||
@implementation GTMLogStandardFormatter
|
||||
|
||||
- (id)init {
|
||||
if ((self = [super init])) {
|
||||
dateFormatter_ = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter_ setFormatterBehavior:NSDateFormatterBehavior10_4];
|
||||
[dateFormatter_ setDateFormat:@"yyyy-MM-dd HH:mm:ss.SSS"];
|
||||
pname_ = [[[NSProcessInfo processInfo] processName] copy];
|
||||
pid_ = [[NSProcessInfo processInfo] processIdentifier];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[dateFormatter_ release];
|
||||
[pname_ release];
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (NSString *)stringForFunc:(NSString *)func
|
||||
withFormat:(NSString *)fmt
|
||||
valist:(va_list)args
|
||||
level:(GTMLoggerLevel)level {
|
||||
GTMLOGGER_ASSERT(dateFormatter_ != nil);
|
||||
NSString *tstamp = nil;
|
||||
@synchronized (dateFormatter_) {
|
||||
tstamp = [dateFormatter_ stringFromDate:[NSDate date]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"%@ %@[%d/%p] [lvl=%d] %@ %@",
|
||||
tstamp, pname_, pid_, pthread_self(),
|
||||
level, (func ? func : @"(no func)"),
|
||||
[super stringForFunc:func withFormat:fmt valist:args level:level]];
|
||||
}
|
||||
|
||||
@end // GTMLogStandardFormatter
|
||||
|
||||
|
||||
@implementation GTMLogLevelFilter
|
||||
|
||||
// Check the environment and the user preferences for the GTMVerboseLogging key
|
||||
// to see if verbose logging has been enabled. The environment variable will
|
||||
// override the defaults setting, so check the environment first.
|
||||
// COV_NF_START
|
||||
static BOOL IsVerboseLoggingEnabled(void) {
|
||||
static NSString *const kVerboseLoggingKey = @"GTMVerboseLogging";
|
||||
static char *env = NULL;
|
||||
if (env == NULL)
|
||||
env = getenv([kVerboseLoggingKey UTF8String]);
|
||||
|
||||
if (env && env[0]) {
|
||||
return (strtol(env, NULL, 10) != 0);
|
||||
}
|
||||
|
||||
return [[NSUserDefaults standardUserDefaults] boolForKey:kVerboseLoggingKey];
|
||||
}
|
||||
// COV_NF_END
|
||||
|
||||
// In DEBUG builds, log everything. If we're not in a debug build we'll assume
|
||||
// that we're in a Release build.
|
||||
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||
#if DEBUG
|
||||
return YES;
|
||||
#endif
|
||||
|
||||
BOOL allow = YES;
|
||||
|
||||
switch (level) {
|
||||
case kGTMLoggerLevelDebug:
|
||||
allow = NO;
|
||||
break;
|
||||
case kGTMLoggerLevelInfo:
|
||||
allow = (IsVerboseLoggingEnabled() == YES);
|
||||
break;
|
||||
case kGTMLoggerLevelError:
|
||||
allow = YES;
|
||||
break;
|
||||
case kGTMLoggerLevelAssert:
|
||||
allow = YES;
|
||||
break;
|
||||
default:
|
||||
allow = YES;
|
||||
break;
|
||||
}
|
||||
|
||||
return allow;
|
||||
}
|
||||
|
||||
@end // GTMLogLevelFilter
|
||||
|
||||
|
||||
@implementation GTMLogNoFilter
|
||||
|
||||
- (BOOL)filterAllowsMessage:(NSString *)msg level:(GTMLoggerLevel)level {
|
||||
return YES; // Allow everything through
|
||||
}
|
||||
|
||||
@end // GTMLogNoFilter
|
||||
304
src/common/mac/MachIPC.h
Normal file
304
src/common/mac/MachIPC.h
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// MachIPC.h
|
||||
//
|
||||
// Some helpful wrappers for using Mach IPC calls
|
||||
|
||||
#ifndef MACH_IPC_H__
|
||||
#define MACH_IPC_H__
|
||||
|
||||
#import <mach/mach.h>
|
||||
#import <mach/message.h>
|
||||
#import <servers/bootstrap.h>
|
||||
#import <sys/types.h>
|
||||
|
||||
#import <CoreServices/CoreServices.h>
|
||||
|
||||
//==============================================================================
|
||||
// DISCUSSION:
|
||||
//
|
||||
// The three main classes of interest are
|
||||
//
|
||||
// MachMessage: a wrapper for a mach message of the following form
|
||||
// mach_msg_header_t
|
||||
// mach_msg_body_t
|
||||
// optional descriptors
|
||||
// optional extra message data
|
||||
//
|
||||
// MachReceiveMessage and MachSendMessage subclass MachMessage
|
||||
// and are used instead of MachMessage which is an abstract base class
|
||||
//
|
||||
// ReceivePort:
|
||||
// Represents a mach port for which we have receive rights
|
||||
//
|
||||
// MachPortSender:
|
||||
// Represents a mach port for which we have send rights
|
||||
//
|
||||
// Here's an example to receive a message on a server port:
|
||||
//
|
||||
// // This creates our named server port
|
||||
// ReceivePort receivePort("com.Google.MyService");
|
||||
//
|
||||
// MachReceiveMessage message;
|
||||
// kern_return_t result = receivePort.WaitForMessage(&message, 0);
|
||||
//
|
||||
// if (result == KERN_SUCCESS && message.GetMessageID() == 57) {
|
||||
// mach_port_t task = message.GetTranslatedPort(0);
|
||||
// mach_port_t thread = message.GetTranslatedPort(1);
|
||||
//
|
||||
// char *messageString = message.GetData();
|
||||
//
|
||||
// printf("message string = %s\n", messageString);
|
||||
// }
|
||||
//
|
||||
// Here is an example of using these classes to send a message to this port:
|
||||
//
|
||||
// // send to already named port
|
||||
// MachPortSender sender("com.Google.MyService");
|
||||
// MachSendMessage message(57); // our message ID is 57
|
||||
//
|
||||
// // add some ports to be translated for us
|
||||
// message.AddDescriptor(mach_task_self()); // our task
|
||||
// message.AddDescriptor(mach_thread_self()); // this thread
|
||||
//
|
||||
// char messageString[] = "Hello server!\n";
|
||||
// message.SetData(messageString, strlen(messageString)+1);
|
||||
//
|
||||
// kern_return_t result = sender.SendMessage(message, 1000); // timeout 1000ms
|
||||
//
|
||||
|
||||
#define PRINT_MACH_RESULT(result_, message_) \
|
||||
printf(message_" %s (%d)\n", mach_error_string(result_), result_ );
|
||||
|
||||
//==============================================================================
|
||||
// A wrapper class for mach_msg_port_descriptor_t (with same memory layout)
|
||||
// with convenient constructors and accessors
|
||||
class MachMsgPortDescriptor : public mach_msg_port_descriptor_t {
|
||||
public:
|
||||
// General-purpose constructor
|
||||
MachMsgPortDescriptor(mach_port_t in_name,
|
||||
mach_msg_type_name_t in_disposition) {
|
||||
name = in_name;
|
||||
pad1 = 0;
|
||||
pad2 = 0;
|
||||
disposition = in_disposition;
|
||||
type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
}
|
||||
|
||||
// For passing send rights to a port
|
||||
MachMsgPortDescriptor(mach_port_t in_name) {
|
||||
name = in_name;
|
||||
pad1 = 0;
|
||||
pad2 = 0;
|
||||
disposition = MACH_MSG_TYPE_COPY_SEND;
|
||||
type = MACH_MSG_PORT_DESCRIPTOR;
|
||||
}
|
||||
|
||||
// Copy constructor
|
||||
MachMsgPortDescriptor(const MachMsgPortDescriptor& desc) {
|
||||
name = desc.name;
|
||||
pad1 = desc.pad1;
|
||||
pad2 = desc.pad2;
|
||||
disposition = desc.disposition;
|
||||
type = desc.type;
|
||||
}
|
||||
|
||||
mach_port_t GetMachPort() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
mach_msg_type_name_t GetDisposition() const {
|
||||
return disposition;
|
||||
}
|
||||
|
||||
// We're just a simple wrapper for mach_msg_port_descriptor_t
|
||||
// and have the same memory layout
|
||||
operator mach_msg_port_descriptor_t&() {
|
||||
return *this;
|
||||
}
|
||||
|
||||
// For convenience
|
||||
operator mach_port_t() const {
|
||||
return GetMachPort();
|
||||
}
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// MachMessage: a wrapper for a mach message
|
||||
// (mach_msg_header_t, mach_msg_body_t, extra data)
|
||||
//
|
||||
// This considerably simplifies the construction of a message for sending
|
||||
// and the getting at relevant data and descriptors for the receiver.
|
||||
//
|
||||
// Currently the combined size of the descriptors plus data must be
|
||||
// less than 1024. But as a benefit no memory allocation is necessary.
|
||||
//
|
||||
// TODO: could consider adding malloc() support for very large messages
|
||||
//
|
||||
// A MachMessage object is used by ReceivePort::WaitForMessage
|
||||
// and MachPortSender::SendMessage
|
||||
//
|
||||
class MachMessage {
|
||||
public:
|
||||
|
||||
// The receiver of the message can retrieve the raw data this way
|
||||
u_int8_t *GetData() {
|
||||
return GetDataLength() > 0 ? GetDataPacket()->data : NULL;
|
||||
}
|
||||
|
||||
u_int32_t GetDataLength() {
|
||||
return EndianU32_LtoN(GetDataPacket()->data_length);
|
||||
}
|
||||
|
||||
// The message ID may be used as a code identifying the type of message
|
||||
void SetMessageID(int32_t message_id) {
|
||||
GetDataPacket()->id = EndianU32_NtoL(message_id);
|
||||
}
|
||||
|
||||
int32_t GetMessageID() { return EndianU32_LtoN(GetDataPacket()->id); }
|
||||
|
||||
// Adds a descriptor (typically a mach port) to be translated
|
||||
// returns true if successful, otherwise not enough space
|
||||
bool AddDescriptor(const MachMsgPortDescriptor &desc);
|
||||
|
||||
int GetDescriptorCount() const { return body.msgh_descriptor_count; }
|
||||
MachMsgPortDescriptor *GetDescriptor(int n);
|
||||
|
||||
// Convenience method which gets the mach port described by the descriptor
|
||||
mach_port_t GetTranslatedPort(int n);
|
||||
|
||||
// A simple message is one with no descriptors
|
||||
bool IsSimpleMessage() const { return GetDescriptorCount() == 0; }
|
||||
|
||||
// Sets raw data for the message (returns false if not enough space)
|
||||
bool SetData(void *data, int32_t data_length);
|
||||
|
||||
protected:
|
||||
// Consider this an abstract base class - must create an actual instance
|
||||
// of MachReceiveMessage or MachSendMessage
|
||||
|
||||
MachMessage() {
|
||||
memset(this, 0, sizeof(MachMessage));
|
||||
}
|
||||
|
||||
friend class ReceivePort;
|
||||
friend class MachPortSender;
|
||||
|
||||
// Represents raw data in our message
|
||||
struct MessageDataPacket {
|
||||
int32_t id; // little-endian
|
||||
int32_t data_length; // little-endian
|
||||
u_int8_t data[1]; // actual size limited by sizeof(MachMessage)
|
||||
};
|
||||
|
||||
MessageDataPacket* GetDataPacket();
|
||||
|
||||
void SetDescriptorCount(int n);
|
||||
void SetDescriptor(int n, const MachMsgPortDescriptor &desc);
|
||||
|
||||
// Returns total message size setting msgh_size in the header to this value
|
||||
int CalculateSize();
|
||||
|
||||
mach_msg_header_t head;
|
||||
mach_msg_body_t body;
|
||||
u_int8_t padding[1024]; // descriptors and data may be embedded here
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// MachReceiveMessage and MachSendMessage are useful to separate the idea
|
||||
// of a mach message being sent and being received, and adds increased type
|
||||
// safety:
|
||||
// ReceivePort::WaitForMessage() only accepts a MachReceiveMessage
|
||||
// MachPortSender::SendMessage() only accepts a MachSendMessage
|
||||
|
||||
//==============================================================================
|
||||
class MachReceiveMessage : public MachMessage {
|
||||
public:
|
||||
MachReceiveMessage() : MachMessage() {};
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class MachSendMessage : public MachMessage {
|
||||
public:
|
||||
MachSendMessage(int32_t message_id);
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Represents a mach port for which we have receive rights
|
||||
class ReceivePort {
|
||||
public:
|
||||
// Creates a new mach port for receiving messages and registers a name for it
|
||||
ReceivePort(const char *receive_port_name);
|
||||
|
||||
// Given an already existing mach port, use it. We take ownership of the
|
||||
// port and deallocate it in our destructor.
|
||||
ReceivePort(mach_port_t receive_port);
|
||||
|
||||
// Create a new mach port for receiving messages
|
||||
ReceivePort();
|
||||
|
||||
~ReceivePort();
|
||||
|
||||
// Waits on the mach port until message received or timeout
|
||||
kern_return_t WaitForMessage(MachReceiveMessage *out_message,
|
||||
mach_msg_timeout_t timeout);
|
||||
|
||||
// The underlying mach port that we wrap
|
||||
mach_port_t GetPort() const { return port_; }
|
||||
|
||||
private:
|
||||
ReceivePort(const ReceivePort&); // disable copy c-tor
|
||||
|
||||
mach_port_t port_;
|
||||
kern_return_t init_result_;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// Represents a mach port for which we have send rights
|
||||
class MachPortSender {
|
||||
public:
|
||||
// get a port with send rights corresponding to a named registered service
|
||||
MachPortSender(const char *receive_port_name);
|
||||
|
||||
|
||||
// Given an already existing mach port, use it.
|
||||
MachPortSender(mach_port_t send_port);
|
||||
|
||||
kern_return_t SendMessage(MachSendMessage &message,
|
||||
mach_msg_timeout_t timeout);
|
||||
|
||||
private:
|
||||
MachPortSender(const MachPortSender&); // disable copy c-tor
|
||||
|
||||
mach_port_t send_port_;
|
||||
kern_return_t init_result_;
|
||||
};
|
||||
|
||||
#endif // MACH_IPC_H__
|
||||
297
src/common/mac/MachIPC.mm
Normal file
297
src/common/mac/MachIPC.mm
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// MachIPC.mm
|
||||
// Wrapper for mach IPC calls
|
||||
|
||||
#import <stdio.h>
|
||||
#import "MachIPC.h"
|
||||
|
||||
//==============================================================================
|
||||
MachSendMessage::MachSendMessage(int32_t message_id) : MachMessage() {
|
||||
head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
|
||||
|
||||
// head.msgh_remote_port = ...; // filled out in MachPortSender::SendMessage()
|
||||
head.msgh_local_port = MACH_PORT_NULL;
|
||||
head.msgh_reserved = 0;
|
||||
head.msgh_id = 0;
|
||||
|
||||
SetDescriptorCount(0); // start out with no descriptors
|
||||
|
||||
SetMessageID(message_id);
|
||||
SetData(NULL, 0); // client may add data later
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// returns true if successful
|
||||
bool MachMessage::SetData(void *data,
|
||||
int32_t data_length) {
|
||||
// first check to make sure we have enough space
|
||||
int size = CalculateSize();
|
||||
int new_size = size + data_length;
|
||||
|
||||
if ((unsigned)new_size > sizeof(MachMessage)) {
|
||||
return false; // not enough space
|
||||
}
|
||||
|
||||
GetDataPacket()->data_length = EndianU32_NtoL(data_length);
|
||||
if (data) memcpy(GetDataPacket()->data, data, data_length);
|
||||
|
||||
CalculateSize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// calculates and returns the total size of the message
|
||||
// Currently, the entire message MUST fit inside of the MachMessage
|
||||
// messsage size <= sizeof(MachMessage)
|
||||
int MachMessage::CalculateSize() {
|
||||
int size = sizeof(mach_msg_header_t) + sizeof(mach_msg_body_t);
|
||||
|
||||
// add space for MessageDataPacket
|
||||
int32_t alignedDataLength = (GetDataLength() + 3) & ~0x3;
|
||||
size += 2*sizeof(int32_t) + alignedDataLength;
|
||||
|
||||
// add space for descriptors
|
||||
size += GetDescriptorCount() * sizeof(MachMsgPortDescriptor);
|
||||
|
||||
head.msgh_size = size;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MachMessage::MessageDataPacket *MachMessage::GetDataPacket() {
|
||||
int desc_size = sizeof(MachMsgPortDescriptor)*GetDescriptorCount();
|
||||
MessageDataPacket *packet =
|
||||
reinterpret_cast<MessageDataPacket*>(padding + desc_size);
|
||||
|
||||
return packet;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MachMessage::SetDescriptor(int n,
|
||||
const MachMsgPortDescriptor &desc) {
|
||||
MachMsgPortDescriptor *desc_array =
|
||||
reinterpret_cast<MachMsgPortDescriptor*>(padding);
|
||||
desc_array[n] = desc;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// returns true if successful otherwise there was not enough space
|
||||
bool MachMessage::AddDescriptor(const MachMsgPortDescriptor &desc) {
|
||||
// first check to make sure we have enough space
|
||||
int size = CalculateSize();
|
||||
int new_size = size + sizeof(MachMsgPortDescriptor);
|
||||
|
||||
if ((unsigned)new_size > sizeof(MachMessage)) {
|
||||
return false; // not enough space
|
||||
}
|
||||
|
||||
// unfortunately, we need to move the data to allow space for the
|
||||
// new descriptor
|
||||
u_int8_t *p = reinterpret_cast<u_int8_t*>(GetDataPacket());
|
||||
bcopy(p, p+sizeof(MachMsgPortDescriptor), GetDataLength()+2*sizeof(int32_t));
|
||||
|
||||
SetDescriptor(GetDescriptorCount(), desc);
|
||||
SetDescriptorCount(GetDescriptorCount() + 1);
|
||||
|
||||
CalculateSize();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void MachMessage::SetDescriptorCount(int n) {
|
||||
body.msgh_descriptor_count = n;
|
||||
|
||||
if (n > 0) {
|
||||
head.msgh_bits |= MACH_MSGH_BITS_COMPLEX;
|
||||
} else {
|
||||
head.msgh_bits &= ~MACH_MSGH_BITS_COMPLEX;
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MachMsgPortDescriptor *MachMessage::GetDescriptor(int n) {
|
||||
if (n < GetDescriptorCount()) {
|
||||
MachMsgPortDescriptor *desc =
|
||||
reinterpret_cast<MachMsgPortDescriptor*>(padding);
|
||||
return desc + n;
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
mach_port_t MachMessage::GetTranslatedPort(int n) {
|
||||
if (n < GetDescriptorCount()) {
|
||||
return GetDescriptor(n)->GetMachPort();
|
||||
}
|
||||
return MACH_PORT_NULL;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
//==============================================================================
|
||||
// create a new mach port for receiving messages and register a name for it
|
||||
ReceivePort::ReceivePort(const char *receive_port_name) {
|
||||
mach_port_t current_task = mach_task_self();
|
||||
|
||||
init_result_ = mach_port_allocate(current_task,
|
||||
MACH_PORT_RIGHT_RECEIVE,
|
||||
&port_);
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
init_result_ = mach_port_insert_right(current_task,
|
||||
port_,
|
||||
port_,
|
||||
MACH_MSG_TYPE_MAKE_SEND);
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
mach_port_t bootstrap_port = 0;
|
||||
init_result_ = task_get_bootstrap_port(current_task, &bootstrap_port);
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
init_result_ = bootstrap_register(bootstrap_port,
|
||||
const_cast<char*>(receive_port_name),
|
||||
port_);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// create a new mach port for receiving messages
|
||||
ReceivePort::ReceivePort() {
|
||||
mach_port_t current_task = mach_task_self();
|
||||
|
||||
init_result_ = mach_port_allocate(current_task,
|
||||
MACH_PORT_RIGHT_RECEIVE,
|
||||
&port_);
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
init_result_ = mach_port_insert_right(current_task,
|
||||
port_,
|
||||
port_,
|
||||
MACH_MSG_TYPE_MAKE_SEND);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// Given an already existing mach port, use it. We take ownership of the
|
||||
// port and deallocate it in our destructor.
|
||||
ReceivePort::ReceivePort(mach_port_t receive_port)
|
||||
: port_(receive_port),
|
||||
init_result_(KERN_SUCCESS) {
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
ReceivePort::~ReceivePort() {
|
||||
if (init_result_ == KERN_SUCCESS)
|
||||
mach_port_deallocate(mach_task_self(), port_);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
kern_return_t ReceivePort::WaitForMessage(MachReceiveMessage *out_message,
|
||||
mach_msg_timeout_t timeout) {
|
||||
if (!out_message) {
|
||||
return KERN_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
// return any error condition encountered in constructor
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return init_result_;
|
||||
|
||||
out_message->head.msgh_bits = 0;
|
||||
out_message->head.msgh_local_port = port_;
|
||||
out_message->head.msgh_remote_port = MACH_PORT_NULL;
|
||||
out_message->head.msgh_reserved = 0;
|
||||
out_message->head.msgh_id = 0;
|
||||
|
||||
kern_return_t result = mach_msg(&out_message->head,
|
||||
MACH_RCV_MSG | MACH_RCV_TIMEOUT,
|
||||
0,
|
||||
sizeof(MachMessage),
|
||||
port_,
|
||||
timeout, // timeout in ms
|
||||
MACH_PORT_NULL);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
//==============================================================================
|
||||
// get a port with send rights corresponding to a named registered service
|
||||
MachPortSender::MachPortSender(const char *receive_port_name) {
|
||||
mach_port_t bootstrap_port = 0;
|
||||
init_result_ = task_get_bootstrap_port(mach_task_self(), &bootstrap_port);
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return;
|
||||
|
||||
init_result_ = bootstrap_look_up(bootstrap_port,
|
||||
const_cast<char*>(receive_port_name),
|
||||
&send_port_);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
MachPortSender::MachPortSender(mach_port_t send_port)
|
||||
: send_port_(send_port),
|
||||
init_result_(KERN_SUCCESS) {
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
kern_return_t MachPortSender::SendMessage(MachSendMessage &message,
|
||||
mach_msg_timeout_t timeout) {
|
||||
if (message.head.msgh_size == 0) {
|
||||
return KERN_INVALID_VALUE; // just for safety -- never should occur
|
||||
};
|
||||
|
||||
if (init_result_ != KERN_SUCCESS)
|
||||
return init_result_;
|
||||
|
||||
message.head.msgh_remote_port = send_port_;
|
||||
|
||||
kern_return_t result = mach_msg(&message.head,
|
||||
MACH_SEND_MSG | MACH_SEND_TIMEOUT,
|
||||
message.head.msgh_size,
|
||||
0,
|
||||
MACH_PORT_NULL,
|
||||
timeout, // timeout in ms
|
||||
MACH_PORT_NULL);
|
||||
|
||||
return result;
|
||||
}
|
||||
195
src/common/mac/SimpleStringDictionary.h
Normal file
195
src/common/mac/SimpleStringDictionary.h
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// SimpleStringDictionary.h
|
||||
//
|
||||
|
||||
#ifndef SimpleStringDictionary_H__
|
||||
#define SimpleStringDictionary_H__
|
||||
|
||||
#import <string>
|
||||
#import <vector>
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
//==============================================================================
|
||||
// SimpleStringDictionary (and associated class KeyValueEntry) implement a very
|
||||
// basic dictionary container class. It has the property of not making any
|
||||
// memory allocations when getting and setting values. But it is not very
|
||||
// efficient, with calls to get and set values operating in linear time.
|
||||
// It has the additional limitation of having a fairly small fixed capacity of
|
||||
// SimpleStringDictionary::MAX_NUM_ENTRIES entries. An assert() will fire if
|
||||
// the client attempts to set more than this number of key/value pairs.
|
||||
// Ordinarilly a C++ programmer would use something like the std::map template
|
||||
// class, or on the Macintosh would often choose CFDictionary or NSDictionary.
|
||||
// But these dictionary classes may call malloc() during get and set operations.
|
||||
// Google Breakpad requires that no memory allocations be made in code running
|
||||
// in its exception handling thread, so it uses SimpleStringDictionary as the
|
||||
// underlying implementation for the GoogleBreakpad.framework APIs:
|
||||
// GoogleBreakpadSetKeyValue(), GoogleBreakpadKeyValue(), and
|
||||
// GoogleBreakpadRemoveKeyValue()
|
||||
//
|
||||
|
||||
//==============================================================================
|
||||
// KeyValueEntry
|
||||
//
|
||||
// A helper class used by SimpleStringDictionary representing a single
|
||||
// storage cell for a key/value pair. Each key and value string are
|
||||
// limited to MAX_STRING_STORAGE_SIZE-1 bytes (not glyphs). This class
|
||||
// performs no memory allocations. It has methods for setting and getting
|
||||
// key and value strings.
|
||||
//
|
||||
class KeyValueEntry {
|
||||
public:
|
||||
KeyValueEntry() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
KeyValueEntry(const char *key, const char *value) {
|
||||
SetKeyValue(key, value);
|
||||
}
|
||||
|
||||
void SetKeyValue(const char *key, const char *value) {
|
||||
if (!key) {
|
||||
key = "";
|
||||
}
|
||||
if (!value) {
|
||||
value = "";
|
||||
}
|
||||
|
||||
strlcpy(key_, key, sizeof(key_));
|
||||
strlcpy(value_, value, sizeof(value_));
|
||||
}
|
||||
|
||||
void SetValue(const char *value) {
|
||||
if (!value) {
|
||||
value = "";
|
||||
}
|
||||
strlcpy(value_, value, sizeof(value_));
|
||||
};
|
||||
|
||||
// Removes the key/value
|
||||
void Clear() {
|
||||
memset(key_, 0, sizeof(key_));
|
||||
memset(value_, 0, sizeof(value_));
|
||||
}
|
||||
|
||||
bool IsActive() const { return key_[0] != '\0'; }
|
||||
const char *GetKey() const { return key_; }
|
||||
const char *GetValue() const { return value_; }
|
||||
|
||||
// Don't change this without considering the fixed size
|
||||
// of MachMessage (in MachIPC.h)
|
||||
// (see also struct KeyValueMessageData in Inspector.h)
|
||||
enum {MAX_STRING_STORAGE_SIZE = 256};
|
||||
|
||||
private:
|
||||
char key_[MAX_STRING_STORAGE_SIZE];
|
||||
char value_[MAX_STRING_STORAGE_SIZE];
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
// This class is not an efficient dictionary, but for the purposes of breakpad
|
||||
// will be just fine. We're just dealing with ten or so distinct
|
||||
// key/value pairs. The idea is to avoid any malloc() or free() calls
|
||||
// in certain important methods to be called when a process is in a
|
||||
// crashed state. Each key and value string are limited to
|
||||
// KeyValueEntry::MAX_STRING_STORAGE_SIZE-1 bytes (not glyphs). Strings passed
|
||||
// in exceeding this length will be truncated.
|
||||
//
|
||||
class SimpleStringDictionary {
|
||||
public:
|
||||
SimpleStringDictionary() {}; // entries will all be cleared
|
||||
|
||||
// Returns the number of active key/value pairs. The upper limit for this
|
||||
// is MAX_NUM_ENTRIES.
|
||||
int GetCount() const;
|
||||
|
||||
// Given |key|, returns its corresponding |value|.
|
||||
// If |key| is NULL, an assert will fire or NULL will be returned. If |key|
|
||||
// is not found or is an empty string, NULL is returned.
|
||||
const char *GetValueForKey(const char *key);
|
||||
|
||||
// Stores a string |value| represented by |key|. If |key| is NULL or an empty
|
||||
// string, this will assert (or do nothing). If |value| is NULL then
|
||||
// the |key| will be removed. An empty string is OK for |value|.
|
||||
void SetKeyValue(const char *key, const char *value);
|
||||
|
||||
// Given |key|, removes any associated value. It will assert (or do nothing)
|
||||
// if NULL is passed in. It will do nothing if |key| is not found.
|
||||
void RemoveKey(const char *key);
|
||||
|
||||
// This is the maximum number of key/value pairs which may be set in the
|
||||
// dictionary. An assert may fire if more values than this are set.
|
||||
// Don't change this without also changing comment in GoogleBreakpad.h
|
||||
enum {MAX_NUM_ENTRIES = 64};
|
||||
|
||||
private:
|
||||
friend class SimpleStringDictionaryIterator;
|
||||
|
||||
const KeyValueEntry *GetEntry(int i) const;
|
||||
|
||||
KeyValueEntry entries_[MAX_NUM_ENTRIES];
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
class SimpleStringDictionaryIterator {
|
||||
public:
|
||||
SimpleStringDictionaryIterator(const SimpleStringDictionary &dict)
|
||||
: dict_(dict), i_(0) {
|
||||
}
|
||||
|
||||
// Initializes iterator to the beginning (may later call Next() )
|
||||
void Start() {
|
||||
i_ = 0;
|
||||
}
|
||||
|
||||
// like the nextObject method of NSEnumerator (in Cocoa)
|
||||
// returns NULL when there are no more entries
|
||||
//
|
||||
const KeyValueEntry* Next() {
|
||||
for (; i_ < SimpleStringDictionary::MAX_NUM_ENTRIES; ++i_) {
|
||||
const KeyValueEntry *entry = dict_.GetEntry(i_);
|
||||
if (entry->IsActive()) {
|
||||
i_++; // move to next entry for next time
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL; // reached end of array
|
||||
}
|
||||
|
||||
private:
|
||||
const SimpleStringDictionary& dict_;
|
||||
int i_;
|
||||
};
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
#endif // SimpleStringDictionary_H__
|
||||
131
src/common/mac/SimpleStringDictionary.mm
Normal file
131
src/common/mac/SimpleStringDictionary.mm
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
// Copyright (c) 2007, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// SimpleStringDictionary.mm
|
||||
// Simple string dictionary that does not allocate memory
|
||||
//
|
||||
|
||||
#import "SimpleStringDictionary.h"
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
//==============================================================================
|
||||
const KeyValueEntry *SimpleStringDictionary::GetEntry(int i) const {
|
||||
return (i >= 0 && i < MAX_NUM_ENTRIES) ? &entries_[i] : NULL;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int SimpleStringDictionary::GetCount() const {
|
||||
int count = 0;
|
||||
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||
if (entries_[i].IsActive() ) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
const char *SimpleStringDictionary::GetValueForKey(const char *key) {
|
||||
assert(key);
|
||||
if (!key)
|
||||
return NULL;
|
||||
|
||||
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||
KeyValueEntry &entry = entries_[i];
|
||||
if (entry.IsActive() && !strcmp(entry.GetKey(), key)) {
|
||||
return entry.GetValue();
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SimpleStringDictionary::SetKeyValue(const char *key,
|
||||
const char *value) {
|
||||
if (!value) {
|
||||
RemoveKey(key);
|
||||
return;
|
||||
}
|
||||
|
||||
// key must not be NULL
|
||||
assert(key);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
// key must not be empty string
|
||||
assert(key[0] != '\0');
|
||||
if (key[0] == '\0')
|
||||
return;
|
||||
|
||||
int free_index = -1;
|
||||
|
||||
// check if key already exists
|
||||
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||
KeyValueEntry &entry = entries_[i];
|
||||
|
||||
if (entry.IsActive()) {
|
||||
if (!strcmp(entry.GetKey(), key)) {
|
||||
entry.SetValue(value);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Make a note of an empty slot
|
||||
if (free_index == -1) {
|
||||
free_index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if we've run out of space
|
||||
assert(free_index != -1);
|
||||
|
||||
// Put new key into an empty slot (if found)
|
||||
if (free_index != -1) {
|
||||
entries_[free_index].SetKeyValue(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void SimpleStringDictionary::RemoveKey(const char *key) {
|
||||
assert(key);
|
||||
if (!key)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < MAX_NUM_ENTRIES; ++i) {
|
||||
if (!strcmp(entries_[i].GetKey(), key)) {
|
||||
entries_[i].Clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace google_breakpad
|
||||
1004
src/common/mac/testing/GTMSenTestCase.h
Normal file
1004
src/common/mac/testing/GTMSenTestCase.h
Normal file
File diff suppressed because it is too large
Load diff
366
src/common/mac/testing/GTMSenTestCase.m
Normal file
366
src/common/mac/testing/GTMSenTestCase.m
Normal file
|
|
@ -0,0 +1,366 @@
|
|||
//
|
||||
// GTMSenTestCase.m
|
||||
//
|
||||
// Copyright 2007-2008 Google Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
// use this file except in compliance with the License. You may obtain a copy
|
||||
// of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
// License for the specific language governing permissions and limitations under
|
||||
// the License.
|
||||
//
|
||||
|
||||
#import "GTMSenTestCase.h"
|
||||
#import <unistd.h>
|
||||
|
||||
#if !GTM_IPHONE_SDK
|
||||
#import "GTMGarbageCollection.h"
|
||||
#endif // !GTM_IPHONE_SDK
|
||||
|
||||
#if GTM_IPHONE_SDK
|
||||
#import <stdarg.h>
|
||||
|
||||
@interface NSException (GTMSenTestPrivateAdditions)
|
||||
+ (NSException *)failureInFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
reason:(NSString *)reason;
|
||||
@end
|
||||
|
||||
@implementation NSException (GTMSenTestPrivateAdditions)
|
||||
+ (NSException *)failureInFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
reason:(NSString *)reason {
|
||||
NSDictionary *userInfo =
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithInteger:lineNumber], SenTestLineNumberKey,
|
||||
filename, SenTestFilenameKey,
|
||||
nil];
|
||||
|
||||
return [self exceptionWithName:SenTestFailureException
|
||||
reason:reason
|
||||
userInfo:userInfo];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation NSException (GTMSenTestAdditions)
|
||||
|
||||
+ (NSException *)failureInFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason = testDescription;
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
+ (NSException *)failureInCondition:(NSString *)condition
|
||||
isTrue:(BOOL)isTrue
|
||||
inFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason = [NSString stringWithFormat:@"'%@' should be %s. %@",
|
||||
condition, isTrue ? "TRUE" : "FALSE", testDescription];
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
+ (NSException *)failureInEqualityBetweenObject:(id)left
|
||||
andObject:(id)right
|
||||
inFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason =
|
||||
[NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
|
||||
[left description], [right description], testDescription];
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
+ (NSException *)failureInEqualityBetweenValue:(NSValue *)left
|
||||
andValue:(NSValue *)right
|
||||
withAccuracy:(NSValue *)accuracy
|
||||
inFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason;
|
||||
if (accuracy) {
|
||||
reason =
|
||||
[NSString stringWithFormat:@"'%@' should be equal to '%@'. %@",
|
||||
left, right, testDescription];
|
||||
} else {
|
||||
reason =
|
||||
[NSString stringWithFormat:@"'%@' should be equal to '%@' +/-'%@'. %@",
|
||||
left, right, accuracy, testDescription];
|
||||
}
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
+ (NSException *)failureInRaise:(NSString *)expression
|
||||
inFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason = [NSString stringWithFormat:@"'%@' should raise. %@",
|
||||
expression, testDescription];
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
+ (NSException *)failureInRaise:(NSString *)expression
|
||||
exception:(NSException *)exception
|
||||
inFile:(NSString *)filename
|
||||
atLine:(int)lineNumber
|
||||
withDescription:(NSString *)formatString, ... {
|
||||
|
||||
NSString *testDescription = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
testDescription =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
|
||||
NSString *reason;
|
||||
if ([[exception name] isEqualToString:SenTestFailureException]) {
|
||||
// it's our exception, assume it has the right description on it.
|
||||
reason = [exception reason];
|
||||
} else {
|
||||
// not one of our exception, use the exceptions reason and our description
|
||||
reason = [NSString stringWithFormat:@"'%@' raised '%@'. %@",
|
||||
expression, [exception reason], testDescription];
|
||||
}
|
||||
|
||||
return [self failureInFile:filename atLine:lineNumber reason:reason];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
NSString *STComposeString(NSString *formatString, ...) {
|
||||
NSString *reason = @"";
|
||||
if (formatString) {
|
||||
va_list vl;
|
||||
va_start(vl, formatString);
|
||||
reason =
|
||||
[[[NSString alloc] initWithFormat:formatString arguments:vl] autorelease];
|
||||
va_end(vl);
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
|
||||
NSString *const SenTestFailureException = @"SenTestFailureException";
|
||||
NSString *const SenTestFilenameKey = @"SenTestFilenameKey";
|
||||
NSString *const SenTestLineNumberKey = @"SenTestLineNumberKey";
|
||||
|
||||
@interface SenTestCase (SenTestCasePrivate)
|
||||
// our method of logging errors
|
||||
+ (void)printException:(NSException *)exception fromTestName:(NSString *)name;
|
||||
@end
|
||||
|
||||
@implementation SenTestCase
|
||||
- (void)failWithException:(NSException*)exception {
|
||||
[exception raise];
|
||||
}
|
||||
|
||||
- (void)setUp {
|
||||
}
|
||||
|
||||
- (void)performTest:(SEL)sel {
|
||||
currentSelector_ = sel;
|
||||
@try {
|
||||
[self invokeTest];
|
||||
} @catch (NSException *exception) {
|
||||
[[self class] printException:exception
|
||||
fromTestName:NSStringFromSelector(sel)];
|
||||
[exception raise];
|
||||
}
|
||||
}
|
||||
|
||||
+ (void)printException:(NSException *)exception fromTestName:(NSString *)name {
|
||||
NSDictionary *userInfo = [exception userInfo];
|
||||
NSString *filename = [userInfo objectForKey:SenTestFilenameKey];
|
||||
NSNumber *lineNumber = [userInfo objectForKey:SenTestLineNumberKey];
|
||||
NSString *className = NSStringFromClass([self class]);
|
||||
if ([filename length] == 0) {
|
||||
filename = @"Unknown.m";
|
||||
}
|
||||
fprintf(stderr, "%s:%ld: error: -[%s %s] : %s\n",
|
||||
[filename UTF8String],
|
||||
(long)[lineNumber integerValue],
|
||||
[className UTF8String],
|
||||
[name UTF8String],
|
||||
[[exception reason] UTF8String]);
|
||||
fflush(stderr);
|
||||
}
|
||||
|
||||
- (void)invokeTest {
|
||||
NSException *e = nil;
|
||||
@try {
|
||||
// Wrap things in autorelease pools because they may
|
||||
// have an STMacro in their dealloc which may get called
|
||||
// when the pool is cleaned up
|
||||
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
|
||||
// We don't log exceptions here, instead we let the person that called
|
||||
// this log the exception. This ensures they are only logged once but the
|
||||
// outer layers get the exceptions to report counts, etc.
|
||||
@try {
|
||||
[self setUp];
|
||||
@try {
|
||||
[self performSelector:currentSelector_];
|
||||
} @catch (NSException *exception) {
|
||||
e = [exception retain];
|
||||
}
|
||||
[self tearDown];
|
||||
} @catch (NSException *exception) {
|
||||
e = [exception retain];
|
||||
}
|
||||
[pool release];
|
||||
} @catch (NSException *exception) {
|
||||
e = [exception retain];
|
||||
}
|
||||
if (e) {
|
||||
[e autorelease];
|
||||
[e raise];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)tearDown {
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
// This matches the description OCUnit would return to you
|
||||
return [NSString stringWithFormat:@"-[%@ %@]", [self class],
|
||||
NSStringFromSelector(currentSelector_)];
|
||||
}
|
||||
@end
|
||||
|
||||
#endif // GTM_IPHONE_SDK
|
||||
|
||||
@implementation GTMTestCase : SenTestCase
|
||||
- (void)invokeTest {
|
||||
Class devLogClass = NSClassFromString(@"GTMUnitTestDevLog");
|
||||
if (devLogClass) {
|
||||
[devLogClass performSelector:@selector(enableTracking)];
|
||||
[devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
|
||||
|
||||
}
|
||||
[super invokeTest];
|
||||
if (devLogClass) {
|
||||
[devLogClass performSelector:@selector(verifyNoMoreLogsExpected)];
|
||||
[devLogClass performSelector:@selector(disableTracking)];
|
||||
}
|
||||
}
|
||||
@end
|
||||
|
||||
// Leak detection
|
||||
#if !GTM_IPHONE_DEVICE
|
||||
// Don't want to get leaks on the iPhone Device as the device doesn't
|
||||
// have 'leaks'. The simulator does though.
|
||||
|
||||
// COV_NF_START
|
||||
// We don't have leak checking on by default, so this won't be hit.
|
||||
static void _GTMRunLeaks(void) {
|
||||
// This is an atexit handler. It runs leaks for us to check if we are
|
||||
// leaking anything in our tests.
|
||||
const char* cExclusionsEnv = getenv("GTM_LEAKS_SYMBOLS_TO_IGNORE");
|
||||
NSMutableString *exclusions = [NSMutableString string];
|
||||
if (cExclusionsEnv) {
|
||||
NSString *exclusionsEnv = [NSString stringWithUTF8String:cExclusionsEnv];
|
||||
NSArray *exclusionsArray = [exclusionsEnv componentsSeparatedByString:@","];
|
||||
NSString *exclusion;
|
||||
NSCharacterSet *wcSet = [NSCharacterSet whitespaceCharacterSet];
|
||||
GTM_FOREACH_OBJECT(exclusion, exclusionsArray) {
|
||||
exclusion = [exclusion stringByTrimmingCharactersInSet:wcSet];
|
||||
[exclusions appendFormat:@"-exclude \"%@\" ", exclusion];
|
||||
}
|
||||
}
|
||||
NSString *string
|
||||
= [NSString stringWithFormat:@"/usr/bin/leaks %@%d"
|
||||
@"| /usr/bin/sed -e 's/Leak: /Leaks:0: warning: Leak /'",
|
||||
exclusions, getpid()];
|
||||
int ret = system([string UTF8String]);
|
||||
if (ret) {
|
||||
fprintf(stderr, "%s:%d: Error: Unable to run leaks. 'system' returned: %d",
|
||||
__FILE__, __LINE__, ret);
|
||||
fflush(stderr);
|
||||
}
|
||||
}
|
||||
// COV_NF_END
|
||||
|
||||
static __attribute__((constructor)) void _GTMInstallLeaks(void) {
|
||||
BOOL checkLeaks = YES;
|
||||
#if !GTM_IPHONE_SDK
|
||||
checkLeaks = GTMIsGarbageCollectionEnabled() ? NO : YES;
|
||||
#endif // !GTM_IPHONE_SDK
|
||||
if (checkLeaks) {
|
||||
checkLeaks = getenv("GTM_ENABLE_LEAKS") ? YES : NO;
|
||||
if (checkLeaks) {
|
||||
// COV_NF_START
|
||||
// We don't have leak checking on by default, so this won't be hit.
|
||||
fprintf(stderr, "Leak Checking Enabled\n");
|
||||
fflush(stderr);
|
||||
int ret = atexit(&_GTMRunLeaks);
|
||||
_GTMDevAssert(ret == 0,
|
||||
@"Unable to install _GTMRunLeaks as an atexit handler (%d)",
|
||||
errno);
|
||||
// COV_NF_END
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // !GTM_IPHONE_DEVICE
|
||||
Loading…
Add table
Add a link
Reference in a new issue