From 10e9a799d19078b9e192b511e692270b08a39648 Mon Sep 17 00:00:00 2001 From: "floitsch@google.com" Date: Tue, 15 Oct 2013 16:02:07 +0000 Subject: [PATCH] Make print interceptable. BUG= http://dartbug.com/3486 R=lrn@google.com Review URL: https://codereview.chromium.org//27112002 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@28650 260f80e4-7a28-3924-810f-c04153c831b5 --- runtime/bin/dartutils.cc | 12 ++--- runtime/lib/collection_dev_sources.gypi | 1 + runtime/lib/corelib_sources.gypi | 1 - runtime/lib/print_patch.dart | 4 +- sdk/lib/_collection_dev/collection_dev.dart | 2 +- .../collection_dev_sources.gypi | 1 + sdk/lib/_collection_dev/print.dart | 17 +++++++ .../_internal/lib/collection_dev_patch.dart | 38 ++++++++++++++++ sdk/lib/_internal/lib/core_patch.dart | 4 -- sdk/lib/_internal/lib/js_helper.dart | 36 --------------- sdk/lib/async/async.dart | 2 +- sdk/lib/async/zone.dart | 43 ++++++++++++++++++ sdk/lib/core/print.dart | 9 +++- tests/compiler/dart2js/mirrors_used_test.dart | 2 +- tests/lib/async/intercept_print1_test.dart | 44 +++++++++++++++++++ 15 files changed, 164 insertions(+), 52 deletions(-) create mode 100644 sdk/lib/_collection_dev/print.dart create mode 100644 tests/lib/async/intercept_print1_test.dart diff --git a/runtime/bin/dartutils.cc b/runtime/bin/dartutils.cc index 0cc4351b037..41a06e6348d 100644 --- a/runtime/bin/dartutils.cc +++ b/runtime/bin/dartutils.cc @@ -660,13 +660,13 @@ Dart_Handle DartUtils::LoadSource(CommandLineOptions* url_mapping, Dart_Handle DartUtils::PrepareForScriptLoading(const char* package_root, Dart_Handle builtin_lib) { - Dart_Handle corelib = Dart_LookupLibrary(NewString("dart:core")); - DART_CHECK_VALID(corelib); - - // Setup the corelib 'print' function. + // Setup the internal library's 'internalPrint' function. + Dart_Handle internal_lib = + Dart_LookupLibrary(NewString("dart:_collection-dev")); + DART_CHECK_VALID(internal_lib); Dart_Handle print = Dart_Invoke( builtin_lib, NewString("_getPrintClosure"), 0, NULL); - Dart_Handle result = Dart_SetField(corelib, + Dart_Handle result = Dart_SetField(internal_lib, NewString("_printClosure"), print); DART_CHECK_VALID(result); @@ -685,6 +685,8 @@ Dart_Handle DartUtils::PrepareForScriptLoading(const char* package_root, async_lib, NewString("_setTimerFactoryClosure"), 1, args)); // Setup the corelib 'Uri.base' getter. + Dart_Handle corelib = Dart_LookupLibrary(NewString("dart:core")); + DART_CHECK_VALID(corelib); Dart_Handle uri_base = Dart_Invoke( builtin_lib, NewString("_getUriBaseClosure"), 0, NULL); DART_CHECK_VALID(uri_base); diff --git a/runtime/lib/collection_dev_sources.gypi b/runtime/lib/collection_dev_sources.gypi index 76121f5426e..63c909fcfc7 100644 --- a/runtime/lib/collection_dev_sources.gypi +++ b/runtime/lib/collection_dev_sources.gypi @@ -8,6 +8,7 @@ 'sources': [ 'collection_dev_patch.dart', # The above file needs to be first as it imports required libraries. + 'print_patch.dart', 'symbol_patch.dart', ], } diff --git a/runtime/lib/corelib_sources.gypi b/runtime/lib/corelib_sources.gypi index 3d50b4034f5..95a846d5299 100644 --- a/runtime/lib/corelib_sources.gypi +++ b/runtime/lib/corelib_sources.gypi @@ -38,7 +38,6 @@ 'null_patch.dart', 'object.cc', 'object_patch.dart', - 'print_patch.dart', 'regexp.cc', 'regexp_jsc.cc', 'regexp_jsc.h', diff --git a/runtime/lib/print_patch.dart b/runtime/lib/print_patch.dart index 53d034c4fee..186b4fb8ece 100644 --- a/runtime/lib/print_patch.dart +++ b/runtime/lib/print_patch.dart @@ -4,8 +4,8 @@ typedef void _PrintClosure(Object obj); -patch void print(Object obj) { - _printClosure(obj.toString()); +patch void printToConsole(String line) { + _printClosure(line); } void _unsupportedPrint(Object obj) { diff --git a/sdk/lib/_collection_dev/collection_dev.dart b/sdk/lib/_collection_dev/collection_dev.dart index 30a7c5ce678..d82d903ee44 100644 --- a/sdk/lib/_collection_dev/collection_dev.dart +++ b/sdk/lib/_collection_dev/collection_dev.dart @@ -14,6 +14,6 @@ import 'dart:collection' show HashSet; part 'arrays.dart'; part 'iterable.dart'; part 'list.dart'; +part 'print.dart'; part 'sort.dart'; part 'symbol.dart'; - diff --git a/sdk/lib/_collection_dev/collection_dev_sources.gypi b/sdk/lib/_collection_dev/collection_dev_sources.gypi index 9cf54151257..c85469de9fe 100644 --- a/sdk/lib/_collection_dev/collection_dev_sources.gypi +++ b/sdk/lib/_collection_dev/collection_dev_sources.gypi @@ -10,6 +10,7 @@ 'arrays.dart', 'iterable.dart', 'list.dart', + 'print.dart', 'sort.dart', 'symbol.dart', ], diff --git a/sdk/lib/_collection_dev/print.dart b/sdk/lib/_collection_dev/print.dart new file mode 100644 index 00000000000..0c7182e0ad3 --- /dev/null +++ b/sdk/lib/_collection_dev/print.dart @@ -0,0 +1,17 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of dart._collection.dev; + +/** + * This function is set by the first allocation of a Zone. + * + * Once the function is set the core `print` function calls this closure instead + * of [printToConsole]. + * + * This decouples the core library from the async library. + */ +Function printToZone = null; + +external void printToConsole(String line); diff --git a/sdk/lib/_internal/lib/collection_dev_patch.dart b/sdk/lib/_internal/lib/collection_dev_patch.dart index f224f24aab6..f7467fd7256 100644 --- a/sdk/lib/_internal/lib/collection_dev_patch.dart +++ b/sdk/lib/_internal/lib/collection_dev_patch.dart @@ -2,7 +2,45 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. +import 'dart:_foreign_helper' show JS; + patch class Symbol implements core.Symbol { patch const Symbol(String name) : this._name = name; } + +/** + * This is the low-level method that is used to implement + * [print]. It is possible to override this function from JavaScript + * by defining a function in JavaScript called "dartPrint". + */ +patch void printToConsole(String line) { + if (JS('bool', r'typeof dartPrint == "function"')) { + // Support overriding print from JavaScript. + JS('void', r'dartPrint(#)', line); + return; + } + + // Inside browser or nodejs. + if (JS('bool', r'typeof console == "object"') && + JS('bool', r'typeof console.log == "function"')) { + JS('void', r'console.log(#)', line); + return; + } + + // Don't throw inside IE, the console is only defined if dev tools is open. + if (JS('bool', r'typeof window == "object"')) { + return; + } + + // Running in d8, the V8 developer shell, or in Firefox' js-shell. + if (JS('bool', r'typeof print == "function"')) { + JS('void', r'print(#)', line); + return; + } + + // This is somewhat nasty, but we don't want to drag in a bunch of + // dependencies to handle a situation that cannot happen. So we + // avoid using Dart [:throw:] and Dart [toString]. + JS('void', 'throw "Unable to print message: " + String(#)', line); +} diff --git a/sdk/lib/_internal/lib/core_patch.dart b/sdk/lib/_internal/lib/core_patch.dart index e14786a8842..cb844e0410f 100644 --- a/sdk/lib/_internal/lib/core_patch.dart +++ b/sdk/lib/_internal/lib/core_patch.dart @@ -24,10 +24,6 @@ _symbolMapToStringMap(Map map) { return result; } -patch void print(Object object) { - Primitives.printString(object.toString()); -} - patch int identityHashCode(Object object) => objectHashCode(object); // Patch for Object implementation. diff --git a/sdk/lib/_internal/lib/js_helper.dart b/sdk/lib/_internal/lib/js_helper.dart index e346734f1b1..1890de0cb0f 100644 --- a/sdk/lib/_internal/lib/js_helper.dart +++ b/sdk/lib/_internal/lib/js_helper.dart @@ -281,42 +281,6 @@ class Primitives { static computeGlobalThis() => JS('', 'function() { return this; }()'); - /** - * This is the low-level method that is used to implement - * [print]. It is possible to override this function from JavaScript - * by defining a function in JavaScript called "dartPrint". - */ - static void printString(String string) { - if (JS('bool', r'typeof dartPrint == "function"')) { - // Support overriding print from JavaScript. - JS('void', r'dartPrint(#)', string); - return; - } - - // Inside browser or nodejs. - if (JS('bool', r'typeof console == "object"') && - JS('bool', r'typeof console.log == "function"')) { - JS('void', r'console.log(#)', string); - return; - } - - // Don't throw inside IE, the console is only defined if dev tools is open. - if (JS('bool', r'typeof window == "object"')) { - return; - } - - // Running in d8, the V8 developer shell, or in Firefox' js-shell. - if (JS('bool', r'typeof print == "function"')) { - JS('void', r'print(#)', string); - return; - } - - // This is somewhat nasty, but we don't want to drag in a bunch of - // dependencies to handle a situation that cannot happen. So we - // avoid using Dart [:throw:] and Dart [toString]. - JS('void', 'throw "Unable to print message: " + String(#)', string); - } - static _throwFormatException(String string) { throw new FormatException(string); } diff --git a/sdk/lib/async/async.dart b/sdk/lib/async/async.dart index 4d03ccd71e2..f336c6066b8 100644 --- a/sdk/lib/async/async.dart +++ b/sdk/lib/async/async.dart @@ -18,7 +18,7 @@ library dart.async; import "dart:collection"; -import "dart:_collection-dev" show deprecated; +import "dart:_collection-dev" show deprecated, printToZone, printToConsole; part 'async_error.dart'; part 'broadcast_stream_controller.dart'; diff --git a/sdk/lib/async/zone.dart b/sdk/lib/async/zone.dart index 7dcff44164f..f419ca088ba 100644 --- a/sdk/lib/async/zone.dart +++ b/sdk/lib/async/zone.dart @@ -31,6 +31,8 @@ typedef Timer CreateTimerHandler( typedef Timer CreatePeriodicTimerHandler( Zone self, ZoneDelegate parent, Zone zone, Duration period, void f(Timer timer)); +typedef void PrintHandler( + Zone self, ZoneDelegate parent, Zone zone, String line); typedef Zone ForkHandler(Zone self, ZoneDelegate parent, Zone zone, ZoneSpecification specification, Map zoneValues); @@ -82,6 +84,7 @@ abstract class ZoneSpecification { Duration duration, void f()): null, Timer createPeriodicTimer(Zone self, ZoneDelegate parent, Zone zone, Duration period, void f(Timer timer)): null, + void print(Zone self, ZoneDelegate parent, Zone zone, String line): null, Zone fork(Zone self, ZoneDelegate parent, Zone zone, ZoneSpecification specification, Map zoneValues): null }) = _ZoneSpecification; @@ -112,6 +115,7 @@ abstract class ZoneSpecification { Duration duration, void f()): null, Timer createPeriodicTimer(Zone self, ZoneDelegate parent, Zone zone, Duration period, void f(Timer timer)): null, + void print(Zone self, ZoneDelegate parent, Zone zone, String line): null, Zone fork(Zone self, ZoneDelegate parent, Zone zone, ZoneSpecification specification, Map zoneValues): null @@ -141,6 +145,7 @@ abstract class ZoneSpecification { createPeriodicTimer: createPeriodicTimer != null ? createPeriodicTimer : other.createPeriodicTimer, + print : print != null ? print : other.print, fork: fork != null ? fork : other.fork); } @@ -156,6 +161,7 @@ abstract class ZoneSpecification { RunAsyncHandler get runAsync; CreateTimerHandler get createTimer; CreatePeriodicTimerHandler get createPeriodicTimer; + PrintHandler get print; ForkHandler get fork; } @@ -179,6 +185,7 @@ class _ZoneSpecification implements ZoneSpecification { this.runAsync: null, this.createTimer: null, this.createPeriodicTimer: null, + this.print: null, this.fork: null }); @@ -195,6 +202,7 @@ class _ZoneSpecification implements ZoneSpecification { final /*RunAsyncHandler*/ runAsync; final /*CreateTimerHandler*/ createTimer; final /*CreatePeriodicTimerHandler*/ createPeriodicTimer; + final /*PrintHandler*/ print; final /*ForkHandler*/ fork; } @@ -224,6 +232,7 @@ abstract class ZoneDelegate { void scheduleMicrotask(Zone zone, f()); Timer createTimer(Zone zone, Duration duration, void f()); Timer createPeriodicTimer(Zone zone, Duration period, void f(Timer timer)); + void print(Zone zone, String line); Zone fork(Zone zone, ZoneSpecification specification, Map zoneValues); } @@ -385,6 +394,11 @@ abstract class Zone { */ Timer createPeriodicTimer(Duration period, void callback(Timer timer)); + /** + * Prints the given [line]. + */ + void print(String line); + /** * The error zone is the one that is responsible for dealing with uncaught * errors. Errors are not allowed to cross zones with different error-zones. @@ -507,6 +521,15 @@ class _ZoneDelegate implements ZoneDelegate { parent, new _ZoneDelegate(parent.parent), zone, period, f); } + void print(Zone zone, String line) { + _CustomizedZone parent = _degelationTarget; + while (parent._specification.print == null) { + parent = parent.parent; + } + (parent._specification.print)( + parent, new _ZoneDelegate(parent.parent), zone, line); + } + Zone fork(Zone zone, ZoneSpecification specification, Map zoneValues) { _BaseZone parent = _degelationTarget; @@ -670,6 +693,10 @@ class _CustomizedZone extends _BaseZone { Timer createPeriodicTimer(Duration duration, void f(Timer timer)) { return new _ZoneDelegate(this).createPeriodicTimer(this, duration, f); } + + void print(String line) { + new _ZoneDelegate(this).print(this, line); + } } void _rootHandleUncaughtError( @@ -754,9 +781,22 @@ Timer _rootCreatePeriodicTimer( return _createPeriodicTimer(duration, callback); } +void _rootPrint(Zone self, ZoneDelegate parent, Zone zone, String line) { + printToConsole(line); +} + +void _printToZone(String line) { + Zone.current.print(line); +} + Zone _rootFork(Zone self, ZoneDelegate parent, Zone zone, ZoneSpecification specification, Map zoneValues) { + // TODO(floitsch): it would be nice if we could get rid of this hack. + // Change the static zoneOrDirectPrint function to go through zones + // from now on. + printToZone = _printToZone; + if (specification == null) { specification = const ZoneSpecification(); } else if (specification is! _ZoneSpecification) { @@ -794,6 +834,7 @@ class _RootZoneSpecification implements ZoneSpecification { CreateTimerHandler get createTimer => _rootCreateTimer; CreatePeriodicTimerHandler get createPeriodicTimer => _rootCreatePeriodicTimer; + PrintHandler get print => _rootPrint; ForkHandler get fork => _rootFork; } @@ -846,6 +887,8 @@ class _RootZone extends _BaseZone { Timer createPeriodicTimer(Duration duration, void f(Timer timer)) => _rootCreatePeriodicTimer(this, null, this, duration, f); + + void print(String line) => _rootPrint(this, null, this, line); } const _ROOT_ZONE = const _RootZone(); diff --git a/sdk/lib/core/print.dart b/sdk/lib/core/print.dart index 17ef81707af..4379ceb47d7 100644 --- a/sdk/lib/core/print.dart +++ b/sdk/lib/core/print.dart @@ -4,4 +4,11 @@ part of dart.core; -external void print(Object object); +void print(Object object) { + String line = object.toString(); + if (printToZone == null) { + printToConsole(line); + } else { + printToZone(line); + } +} diff --git a/tests/compiler/dart2js/mirrors_used_test.dart b/tests/compiler/dart2js/mirrors_used_test.dart index 25ac037891a..c425627ba66 100644 --- a/tests/compiler/dart2js/mirrors_used_test.dart +++ b/tests/compiler/dart2js/mirrors_used_test.dart @@ -59,7 +59,7 @@ void main() { // 2. Some code was refactored, and there are more methods. // Either situation could be problematic, but in situation 2, it is often // acceptable to increase [expectedMethodCount] a little. - int expectedMethodCount = 326; + int expectedMethodCount = 327; Expect.isTrue( generatedCode.length <= expectedMethodCount, 'Too many compiled methods: ' diff --git a/tests/lib/async/intercept_print1_test.dart b/tests/lib/async/intercept_print1_test.dart new file mode 100644 index 00000000000..ad074c0df80 --- /dev/null +++ b/tests/lib/async/intercept_print1_test.dart @@ -0,0 +1,44 @@ +// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import "package:expect/expect.dart"; +import 'dart:async'; +import 'catch_errors.dart'; + +var events = []; + +void printHandler1(Zone self, ZoneDelegate parent, Zone origin, String line) { + events.add("print: $line"); +} + +bool shouldIntercept = true; + +void printHandler2(Zone self, ZoneDelegate parent, Zone origin, String line) { + if (shouldIntercept) { + events.add("print **: $line"); + } else { + parent.print(origin, line); + } +} + +const TEST_SPEC1 = const ZoneSpecification(print: printHandler1); +const TEST_SPEC2 = const ZoneSpecification(print: printHandler2); + +main() { + Zone zone1 = Zone.current.fork(specification: TEST_SPEC1); + Zone zone2 = zone1.fork(specification: TEST_SPEC2); + zone1.run(() { + print("1"); + print(2); + print({3: [4]}); + }); + zone2.run(() { + print("5"); + shouldIntercept = false; + print(6); + }); + Expect.listEquals( + ["print: 1", "print: 2", "print: {3: [4]}", "print **: 5", "print: 6"], + events); +}