diff options
author | Feng Qian <fqian@google.com> | 2009-04-16 10:13:32 -0700 |
---|---|---|
committer | Feng Qian <fqian@google.com> | 2009-04-16 10:13:32 -0700 |
commit | e62a68bd8f6c9cb81ff8757c55d64ae13a277eab (patch) | |
tree | 93d1837c5c326c12d859500aa63c0db55fc7e985 /v8/test/cctest | |
parent | 38b27eb8bd9bb83aa3b8eb1d48efcab50bff6a5a (diff) | |
download | external_webkit-e62a68bd8f6c9cb81ff8757c55d64ae13a277eab.zip external_webkit-e62a68bd8f6c9cb81ff8757c55d64ae13a277eab.tar.gz external_webkit-e62a68bd8f6c9cb81ff8757c55d64ae13a277eab.tar.bz2 |
Import V8 r1682 from trunk.
v8/REVISION has svn URL and revision number:
http://v8.googlecode.com/svn/trunk@1682
Diffstat (limited to 'v8/test/cctest')
34 files changed, 18181 insertions, 0 deletions
diff --git a/v8/test/cctest/SConscript b/v8/test/cctest/SConscript new file mode 100644 index 0000000..091768f --- /dev/null +++ b/v8/test/cctest/SConscript @@ -0,0 +1,84 @@ +# Copyright 2008 the V8 project authors. 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. + +import sys +from os.path import join, dirname, abspath +root_dir = dirname(File('SConstruct').rfile().abspath) +sys.path.append(join(root_dir, 'tools')) +Import('context object_files') + + +SOURCES = { + 'all': [ + 'test-alloc.cc', + 'test-api.cc', + 'test-ast.cc', + 'test-compiler.cc', + 'test-conversions.cc', + 'test-debug.cc', + 'test-decls.cc', + 'test-flags.cc', + 'test-hashmap.cc', + 'test-heap.cc', + 'test-list.cc', + 'test-lock.cc', + 'test-mark-compact.cc', + 'test-regexp.cc', + 'test-serialize.cc', + 'test-sockets.cc', + 'test-spaces.cc', + 'test-strings.cc', + 'test-threads.cc', + 'test-utils.cc' + ], + 'arch:arm': ['test-assembler-arm.cc', 'test-disasm-arm.cc'], + 'arch:ia32': [ + 'test-assembler-ia32.cc', + 'test-disasm-ia32.cc', + 'test-log-ia32.cc' + ], + 'os:linux': ['test-platform-linux.cc'], + 'os:macos': ['test-platform-macos.cc'], + 'os:nullos': ['test-platform-nullos.cc'], + 'os:win32': ['test-platform-win32.cc'] +} + + +def Build(): + cctest_files = context.GetRelevantSources(SOURCES) + env = Environment() + env.Replace(**context.flags['cctest']) + context.ApplyEnvOverrides(env) + # There seems to be a glitch in the way scons decides where to put + # PDB files when compiling using MSVC so we specify it manually. + # This should not affect any other platforms. + return env.Program('cctest', ['cctest.cc', cctest_files, object_files], + PDB='cctest.exe.pdb') + + +program = Build() +Return('program') diff --git a/v8/test/cctest/cctest.cc b/v8/test/cctest/cctest.cc new file mode 100644 index 0000000..2807c8b --- /dev/null +++ b/v8/test/cctest/cctest.cc @@ -0,0 +1,126 @@ +// Copyright 2008 the V8 project authors. 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. + +#include <v8.h> +#include <cstdlib> +#include <cstring> +#include <cstdio> +#include "cctest.h" +#include "debug.h" + + +CcTest* CcTest::last_ = NULL; + + +CcTest::CcTest(TestFunction* callback, const char* file, const char* name, + const char* dependency, bool enabled) + : callback_(callback), name_(name), dependency_(dependency), prev_(last_) { + // Find the base name of this test (const_cast required on Windows). + char *basename = strrchr(const_cast<char *>(file), '/'); + if (!basename) { + basename = strrchr(const_cast<char *>(file), '\\'); + } + if (!basename) { + basename = v8::internal::StrDup(file); + } else { + basename = v8::internal::StrDup(basename + 1); + } + // Drop the extension, if there is one. + char *extension = strrchr(basename, '.'); + if (extension) *extension = 0; + // Install this test in the list of tests + file_ = basename; + enabled_ = enabled; + prev_ = last_; + last_ = this; +} + + +static void PrintTestList(CcTest* current) { + if (current == NULL) return; + PrintTestList(current->prev()); + if (current->dependency() != NULL) { + printf("%s/%s<%s\n", + current->file(), current->name(), current->dependency()); + } else { + printf("%s/%s<\n", current->file(), current->name()); + } +} + + +int main(int argc, char* argv[]) { + v8::internal::FlagList::SetFlagsFromCommandLine(&argc, argv, true); + int tests_run = 0; + bool print_run_count = true; + for (int i = 1; i < argc; i++) { + char* arg = argv[i]; + if (strcmp(arg, "--list") == 0) { + PrintTestList(CcTest::last()); + print_run_count = false; + + } else { + char* arg_copy = v8::internal::StrDup(arg); + char* testname = strchr(arg_copy, '/'); + if (testname) { + // Split the string in two by nulling the slash and then run + // exact matches. + *testname = 0; + char* file = arg_copy; + char* name = testname + 1; + CcTest* test = CcTest::last(); + while (test != NULL) { + if (test->enabled() + && strcmp(test->file(), file) == 0 + && strcmp(test->name(), name) == 0) { + test->Run(); + tests_run++; + } + test = test->prev(); + } + + } else { + // Run all tests with the specified file or test name. + char* file_or_name = arg_copy; + CcTest* test = CcTest::last(); + while (test != NULL) { + if (test->enabled() + && (strcmp(test->file(), file_or_name) == 0 + || strcmp(test->name(), file_or_name) == 0)) { + test->Run(); + tests_run++; + } + test = test->prev(); + } + } + v8::internal::DeleteArray<char>(arg_copy); + } + } + if (print_run_count && tests_run != 1) + printf("Ran %i tests.\n", tests_run); + v8::V8::Dispose(); + return 0; +} diff --git a/v8/test/cctest/cctest.h b/v8/test/cctest/cctest.h new file mode 100644 index 0000000..a95645e --- /dev/null +++ b/v8/test/cctest/cctest.h @@ -0,0 +1,75 @@ +// Copyright 2008 the V8 project authors. 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. + +#ifndef CCTEST_H_ +#define CCTEST_H_ + +#ifndef TEST +#define TEST(Name) \ + static void Test##Name(); \ + CcTest register_test_##Name(Test##Name, __FILE__, #Name, NULL, true); \ + static void Test##Name() +#endif + +#ifndef DEPENDENT_TEST +#define DEPENDENT_TEST(Name, Dep) \ + static void Test##Name(); \ + CcTest register_test_##Name(Test##Name, __FILE__, #Name, #Dep, true); \ + static void Test##Name() +#endif + +#ifndef DISABLED_TEST +#define DISABLED_TEST(Name) \ + static void Test##Name(); \ + CcTest register_test_##Name(Test##Name, __FILE__, #Name, NULL, false); \ + static void Test##Name() +#endif + +class CcTest { + public: + typedef void (TestFunction)(); + CcTest(TestFunction* callback, const char* file, const char* name, + const char* dependency, bool enabled); + void Run() { callback_(); } + static int test_count(); + static CcTest* last() { return last_; } + CcTest* prev() { return prev_; } + const char* file() { return file_; } + const char* name() { return name_; } + const char* dependency() { return dependency_; } + bool enabled() { return enabled_; } + private: + TestFunction* callback_; + const char* file_; + const char* name_; + const char* dependency_; + bool enabled_; + static CcTest* last_; + CcTest* prev_; +}; + +#endif // ifndef CCTEST_H_ diff --git a/v8/test/cctest/cctest.status b/v8/test/cctest/cctest.status new file mode 100644 index 0000000..9a3f819 --- /dev/null +++ b/v8/test/cctest/cctest.status @@ -0,0 +1,50 @@ +# Copyright 2008 the V8 project authors. 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. + +prefix cctest + +# BUG(96): Fix this flaky test. +test-debug/ThreadedDebugging: PASS || FAIL + +# BUG(281): This test fails on some Linuxes. +test-debug/DebuggerAgent: PASS, (PASS || FAIL) if $system == linux + +[ $arch == arm ] + +test-debug: SKIP + +# BUG(113): Test seems flaky on ARM. +test-spaces/LargeObjectSpace: PASS || FAIL + +# BUG(240): Test seems flaky on ARM. +test-api/RegExpInterruption: SKIP + +# BUG(271): During exception propagation, we compare pointers into the +# stack. These tests fail on the ARM simulator because the C++ and +# the JavaScript stacks are separate. +test-api/ExceptionOrder: PASS || FAIL +test-api/TryCatchInTryFinally: PASS || FAIL diff --git a/v8/test/cctest/test-alloc.cc b/v8/test/cctest/test-alloc.cc new file mode 100644 index 0000000..9996eeb --- /dev/null +++ b/v8/test/cctest/test-alloc.cc @@ -0,0 +1,146 @@ +// Copyright 2007-2008 the V8 project authors. 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. + +#include "v8.h" +#include "accessors.h" +#include "top.h" + +#include "cctest.h" + + +using namespace v8::internal; + + +static Object* AllocateAfterFailures() { + static int attempts = 0; + if (++attempts < 3) return Failure::RetryAfterGC(0); + + // New space. + NewSpace* new_space = Heap::new_space(); + static const int kNewSpaceFillerSize = ByteArray::SizeFor(0); + while (new_space->Available() > kNewSpaceFillerSize) { + CHECK(!Heap::AllocateByteArray(0)->IsFailure()); + } + CHECK(!Heap::AllocateByteArray(100)->IsFailure()); + CHECK(!Heap::AllocateFixedArray(100, NOT_TENURED)->IsFailure()); + + // Make sure we can allocate through optimized allocation functions + // for specific kinds. + CHECK(!Heap::AllocateFixedArray(100)->IsFailure()); + CHECK(!Heap::AllocateHeapNumber(0.42)->IsFailure()); + CHECK(!Heap::AllocateArgumentsObject(Smi::FromInt(87), 10)->IsFailure()); + Object* object = Heap::AllocateJSObject(*Top::object_function()); + CHECK(!Heap::CopyJSObject(JSObject::cast(object))->IsFailure()); + + // Old data space. + OldSpace* old_data_space = Heap::old_data_space(); + static const int kOldDataSpaceFillerSize = SeqAsciiString::SizeFor(0); + while (old_data_space->Available() > kOldDataSpaceFillerSize) { + CHECK(!Heap::AllocateRawAsciiString(0, TENURED)->IsFailure()); + } + CHECK(!Heap::AllocateRawAsciiString(100, TENURED)->IsFailure()); + + // Large object space. + while (!Heap::OldGenerationAllocationLimitReached()) { + CHECK(!Heap::AllocateFixedArray(10000, TENURED)->IsFailure()); + } + CHECK(!Heap::AllocateFixedArray(10000, TENURED)->IsFailure()); + + // Map space. + MapSpace* map_space = Heap::map_space(); + static const int kMapSpaceFillerSize = Map::kSize; + InstanceType instance_type = JS_OBJECT_TYPE; + int instance_size = JSObject::kHeaderSize; + while (map_space->Available() > kMapSpaceFillerSize) { + CHECK(!Heap::AllocateMap(instance_type, instance_size)->IsFailure()); + } + CHECK(!Heap::AllocateMap(instance_type, instance_size)->IsFailure()); + + // Test that we can allocate in old pointer space and code space. + CHECK(!Heap::AllocateFixedArray(100, TENURED)->IsFailure()); + CHECK(!Heap::CopyCode(Builtins::builtin(Builtins::Illegal))->IsFailure()); + + // Return success. + return Smi::FromInt(42); +} + + +static Handle<Object> Test() { + CALL_HEAP_FUNCTION(AllocateAfterFailures(), Object); +} + + +TEST(StressHandles) { + v8::Persistent<v8::Context> env = v8::Context::New(); + v8::HandleScope scope; + env->Enter(); + Handle<Object> o = Test(); + CHECK(o->IsSmi() && Smi::cast(*o)->value() == 42); + env->Exit(); +} + + +static Object* TestAccessorGet(Object* object, void*) { + return AllocateAfterFailures(); +} + + +const AccessorDescriptor kDescriptor = { + TestAccessorGet, + 0, + 0 +}; + + +TEST(StressJS) { + v8::Persistent<v8::Context> env = v8::Context::New(); + v8::HandleScope scope; + env->Enter(); + Handle<JSFunction> function = + Factory::NewFunction(Factory::function_symbol(), Factory::null_value()); + // Force the creation of an initial map and set the code to + // something empty. + Factory::NewJSObject(function); + function->set_code(Builtins::builtin(Builtins::EmptyFunction)); + // Patch the map to have an accessor for "get". + Handle<Map> map(function->initial_map()); + Handle<DescriptorArray> instance_descriptors(map->instance_descriptors()); + Handle<Proxy> proxy = Factory::NewProxy(&kDescriptor); + instance_descriptors = Factory::CopyAppendProxyDescriptor( + instance_descriptors, + Factory::NewStringFromAscii(Vector<const char>("get", 3)), + proxy, + static_cast<PropertyAttributes>(0)); + map->set_instance_descriptors(*instance_descriptors); + // Add the Foo constructor the global object. + env->Global()->Set(v8::String::New("Foo"), v8::Utils::ToLocal(function)); + // Call the accessor through JavaScript. + v8::Handle<v8::Value> result = + v8::Script::Compile(v8::String::New("(new Foo).get"))->Run(); + CHECK_EQ(42, result->Int32Value()); + env->Exit(); +} diff --git a/v8/test/cctest/test-api.cc b/v8/test/cctest/test-api.cc new file mode 100644 index 0000000..dd705d2 --- /dev/null +++ b/v8/test/cctest/test-api.cc @@ -0,0 +1,6148 @@ +// Copyright 2007-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include <map> +#include <string> + +#include "v8.h" + +#include "api.h" +#include "snapshot.h" +#include "platform.h" +#include "top.h" +#include "cctest.h" + +static bool IsNaN(double x) { +#ifdef WIN32 + return _isnan(x); +#else + return isnan(x); +#endif +} + +using ::v8::ObjectTemplate; +using ::v8::Value; +using ::v8::Context; +using ::v8::Local; +using ::v8::String; +using ::v8::Script; +using ::v8::Function; +using ::v8::AccessorInfo; +using ::v8::Extension; + +namespace i = ::v8::internal; + +static Local<Value> v8_num(double x) { + return v8::Number::New(x); +} + + +static Local<String> v8_str(const char* x) { + return String::New(x); +} + + +static Local<Script> v8_compile(const char* x) { + return Script::Compile(v8_str(x)); +} + + +// A LocalContext holds a reference to a v8::Context. +class LocalContext { + public: + LocalContext(v8::ExtensionConfiguration* extensions = 0, + v8::Handle<ObjectTemplate> global_template = + v8::Handle<ObjectTemplate>(), + v8::Handle<Value> global_object = v8::Handle<Value>()) + : context_(Context::New(extensions, global_template, global_object)) { + context_->Enter(); + } + + virtual ~LocalContext() { + context_->Exit(); + context_.Dispose(); + } + + Context* operator->() { return *context_; } + Context* operator*() { return *context_; } + Local<Context> local() { return Local<Context>::New(context_); } + bool IsReady() { return !context_.IsEmpty(); } + + private: + v8::Persistent<Context> context_; +}; + + +// Switches between all the Api tests using the threading support. +// In order to get a surprising but repeatable pattern of thread +// switching it has extra semaphores to control the order in which +// the tests alternate, not relying solely on the big V8 lock. +// +// A test is augmented with calls to ApiTestFuzzer::Fuzz() in its +// callbacks. This will have no effect when we are not running the +// thread fuzzing test. In the thread fuzzing test it will +// pseudorandomly select a successor thread and switch execution +// to that thread, suspending the current test. +class ApiTestFuzzer: public v8::internal::Thread { + public: + void CallTest(); + explicit ApiTestFuzzer(int num) + : test_number_(num), + gate_(v8::internal::OS::CreateSemaphore(0)), + active_(true) { + } + ~ApiTestFuzzer() { delete gate_; } + + // The ApiTestFuzzer is also a Thread, so it has a Run method. + virtual void Run(); + + enum PartOfTest { FIRST_PART, SECOND_PART }; + + static void Setup(PartOfTest part); + static void RunAllTests(); + static void TearDown(); + // This method switches threads if we are running the Threading test. + // Otherwise it does nothing. + static void Fuzz(); + private: + static bool fuzzing_; + static int tests_being_run_; + static int current_; + static int active_tests_; + static bool NextThread(); + int test_number_; + v8::internal::Semaphore* gate_; + bool active_; + void ContextSwitch(); + static int GetNextTestNumber(); + static v8::internal::Semaphore* all_tests_done_; +}; + + +#define THREADED_TEST(Name) \ + static void Test##Name(); \ + RegisterThreadedTest register_##Name(Test##Name); \ + /* */ TEST(Name) + + +class RegisterThreadedTest { + public: + explicit RegisterThreadedTest(CcTest::TestFunction* callback) + : fuzzer_(NULL), callback_(callback) { + prev_ = first_; + first_ = this; + count_++; + } + static int count() { return count_; } + static RegisterThreadedTest* nth(int i) { + ASSERT(i < count()); + RegisterThreadedTest* current = first_; + while (i > 0) { + i--; + current = current->prev_; + } + return current; + } + CcTest::TestFunction* callback() { return callback_; } + ApiTestFuzzer* fuzzer_; + + private: + static RegisterThreadedTest* first_; + static int count_; + CcTest::TestFunction* callback_; + RegisterThreadedTest* prev_; +}; + + +RegisterThreadedTest *RegisterThreadedTest::first_ = NULL; +int RegisterThreadedTest::count_ = 0; + + +static int signature_callback_count; +static v8::Handle<Value> IncrementingSignatureCallback( + const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + signature_callback_count++; + v8::Handle<v8::Array> result = v8::Array::New(args.Length()); + for (int i = 0; i < args.Length(); i++) + result->Set(v8::Integer::New(i), args[i]); + return result; +} + + +static v8::Handle<Value> SignatureCallback(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + v8::Handle<v8::Array> result = v8::Array::New(args.Length()); + for (int i = 0; i < args.Length(); i++) { + result->Set(v8::Integer::New(i), args[i]); + } + return result; +} + + +THREADED_TEST(Handles) { + v8::HandleScope scope; + Local<Context> local_env; + { + LocalContext env; + local_env = env.local(); + } + + // Local context should still be live. + CHECK(!local_env.IsEmpty()); + local_env->Enter(); + + v8::Handle<v8::Primitive> undef = v8::Undefined(); + CHECK(!undef.IsEmpty()); + CHECK(undef->IsUndefined()); + + const char* c_source = "1 + 2 + 3"; + Local<String> source = String::New(c_source); + Local<Script> script = Script::Compile(source); + CHECK_EQ(6, script->Run()->Int32Value()); + + local_env->Exit(); +} + + +// Helper function that compiles and runs the source. +static Local<Value> CompileRun(const char* source) { + return Script::Compile(String::New(source))->Run(); +} + +THREADED_TEST(ReceiverSignature) { + v8::HandleScope scope; + LocalContext env; + v8::Handle<v8::FunctionTemplate> fun = v8::FunctionTemplate::New(); + v8::Handle<v8::Signature> sig = v8::Signature::New(fun); + fun->PrototypeTemplate()->Set( + v8_str("m"), + v8::FunctionTemplate::New(IncrementingSignatureCallback, + v8::Handle<Value>(), + sig)); + env->Global()->Set(v8_str("Fun"), fun->GetFunction()); + signature_callback_count = 0; + CompileRun( + "var o = new Fun();" + "o.m();"); + CHECK_EQ(1, signature_callback_count); + v8::Handle<v8::FunctionTemplate> sub_fun = v8::FunctionTemplate::New(); + sub_fun->Inherit(fun); + env->Global()->Set(v8_str("SubFun"), sub_fun->GetFunction()); + CompileRun( + "var o = new SubFun();" + "o.m();"); + CHECK_EQ(2, signature_callback_count); + + v8::TryCatch try_catch; + CompileRun( + "var o = { };" + "o.m = Fun.prototype.m;" + "o.m();"); + CHECK_EQ(2, signature_callback_count); + CHECK(try_catch.HasCaught()); + try_catch.Reset(); + v8::Handle<v8::FunctionTemplate> unrel_fun = v8::FunctionTemplate::New(); + sub_fun->Inherit(fun); + env->Global()->Set(v8_str("UnrelFun"), unrel_fun->GetFunction()); + CompileRun( + "var o = new UnrelFun();" + "o.m = Fun.prototype.m;" + "o.m();"); + CHECK_EQ(2, signature_callback_count); + CHECK(try_catch.HasCaught()); +} + + + + +THREADED_TEST(ArgumentSignature) { + v8::HandleScope scope; + LocalContext env; + v8::Handle<v8::FunctionTemplate> cons = v8::FunctionTemplate::New(); + cons->SetClassName(v8_str("Cons")); + v8::Handle<v8::Signature> sig = + v8::Signature::New(v8::Handle<v8::FunctionTemplate>(), 1, &cons); + v8::Handle<v8::FunctionTemplate> fun = + v8::FunctionTemplate::New(SignatureCallback, v8::Handle<Value>(), sig); + env->Global()->Set(v8_str("Cons"), cons->GetFunction()); + env->Global()->Set(v8_str("Fun1"), fun->GetFunction()); + + v8::Handle<Value> value1 = CompileRun("Fun1(4) == '';"); + ASSERT(value1->IsTrue()); + + v8::Handle<Value> value2 = CompileRun("Fun1(new Cons()) == '[object Cons]';"); + ASSERT(value2->IsTrue()); + + v8::Handle<Value> value3 = CompileRun("Fun1() == '';"); + ASSERT(value3->IsTrue()); + + v8::Handle<v8::FunctionTemplate> cons1 = v8::FunctionTemplate::New(); + cons1->SetClassName(v8_str("Cons1")); + v8::Handle<v8::FunctionTemplate> cons2 = v8::FunctionTemplate::New(); + cons2->SetClassName(v8_str("Cons2")); + v8::Handle<v8::FunctionTemplate> cons3 = v8::FunctionTemplate::New(); + cons3->SetClassName(v8_str("Cons3")); + + v8::Handle<v8::FunctionTemplate> args[3] = { cons1, cons2, cons3 }; + v8::Handle<v8::Signature> wsig = + v8::Signature::New(v8::Handle<v8::FunctionTemplate>(), 3, args); + v8::Handle<v8::FunctionTemplate> fun2 = + v8::FunctionTemplate::New(SignatureCallback, v8::Handle<Value>(), wsig); + + env->Global()->Set(v8_str("Cons1"), cons1->GetFunction()); + env->Global()->Set(v8_str("Cons2"), cons2->GetFunction()); + env->Global()->Set(v8_str("Cons3"), cons3->GetFunction()); + env->Global()->Set(v8_str("Fun2"), fun2->GetFunction()); + v8::Handle<Value> value4 = CompileRun( + "Fun2(new Cons1(), new Cons2(), new Cons3()) ==" + "'[object Cons1],[object Cons2],[object Cons3]'"); + ASSERT(value4->IsTrue()); + + v8::Handle<Value> value5 = CompileRun( + "Fun2(new Cons1(), new Cons2(), 5) == '[object Cons1],[object Cons2],'"); + ASSERT(value5->IsTrue()); + + v8::Handle<Value> value6 = CompileRun( + "Fun2(new Cons3(), new Cons2(), new Cons1()) == ',[object Cons2],'"); + ASSERT(value6->IsTrue()); + + v8::Handle<Value> value7 = CompileRun( + "Fun2(new Cons1(), new Cons2(), new Cons3(), 'd') == " + "'[object Cons1],[object Cons2],[object Cons3],d';"); + ASSERT(value7->IsTrue()); + + v8::Handle<Value> value8 = CompileRun( + "Fun2(new Cons1(), new Cons2()) == '[object Cons1],[object Cons2]'"); + ASSERT(value8->IsTrue()); +} + + +THREADED_TEST(HulIgennem) { + v8::HandleScope scope; + LocalContext env; + v8::Handle<v8::Primitive> undef = v8::Undefined(); + Local<String> undef_str = undef->ToString(); + char* value = i::NewArray<char>(undef_str->Length() + 1); + undef_str->WriteAscii(value); + CHECK_EQ(0, strcmp(value, "undefined")); + i::DeleteArray(value); +} + + +THREADED_TEST(Access) { + v8::HandleScope scope; + LocalContext env; + Local<v8::Object> obj = v8::Object::New(); + Local<Value> foo_before = obj->Get(v8_str("foo")); + CHECK(foo_before->IsUndefined()); + Local<String> bar_str = v8_str("bar"); + obj->Set(v8_str("foo"), bar_str); + Local<Value> foo_after = obj->Get(v8_str("foo")); + CHECK(!foo_after->IsUndefined()); + CHECK(foo_after->IsString()); + CHECK_EQ(bar_str, foo_after); +} + + +THREADED_TEST(Script) { + v8::HandleScope scope; + LocalContext env; + const char* c_source = "1 + 2 + 3"; + Local<String> source = String::New(c_source); + Local<Script> script = Script::Compile(source); + CHECK_EQ(6, script->Run()->Int32Value()); +} + + +static uint16_t* AsciiToTwoByteString(const char* source) { + size_t array_length = strlen(source) + 1; + uint16_t* converted = i::NewArray<uint16_t>(array_length); + for (size_t i = 0; i < array_length; i++) converted[i] = source[i]; + return converted; +} + + +class TestResource: public String::ExternalStringResource { + public: + static int dispose_count; + + explicit TestResource(uint16_t* data) + : data_(data), length_(0) { + while (data[length_]) ++length_; + } + + ~TestResource() { + i::DeleteArray(data_); + ++dispose_count; + } + + const uint16_t* data() const { + return data_; + } + + size_t length() const { + return length_; + } + private: + uint16_t* data_; + size_t length_; +}; + + +int TestResource::dispose_count = 0; + + +class TestAsciiResource: public String::ExternalAsciiStringResource { + public: + static int dispose_count; + + explicit TestAsciiResource(char* data) + : data_(data), + length_(strlen(data)) { } + + ~TestAsciiResource() { + i::DeleteArray(data_); + ++dispose_count; + } + + const char* data() const { + return data_; + } + + size_t length() const { + return length_; + } + private: + char* data_; + size_t length_; +}; + + +int TestAsciiResource::dispose_count = 0; + + +THREADED_TEST(ScriptUsingStringResource) { + TestResource::dispose_count = 0; + const char* c_source = "1 + 2 * 3"; + uint16_t* two_byte_source = AsciiToTwoByteString(c_source); + { + v8::HandleScope scope; + LocalContext env; + TestResource* resource = new TestResource(two_byte_source); + Local<String> source = String::NewExternal(resource); + Local<Script> script = Script::Compile(source); + Local<Value> value = script->Run(); + CHECK(value->IsNumber()); + CHECK_EQ(7, value->Int32Value()); + CHECK(source->IsExternal()); + CHECK_EQ(resource, + static_cast<TestResource*>(source->GetExternalStringResource())); + v8::internal::Heap::CollectAllGarbage(); + CHECK_EQ(0, TestResource::dispose_count); + } + v8::internal::Heap::CollectAllGarbage(); + CHECK_EQ(1, TestResource::dispose_count); +} + + +THREADED_TEST(ScriptUsingAsciiStringResource) { + TestAsciiResource::dispose_count = 0; + const char* c_source = "1 + 2 * 3"; + { + v8::HandleScope scope; + LocalContext env; + Local<String> source = + String::NewExternal(new TestAsciiResource(i::StrDup(c_source))); + Local<Script> script = Script::Compile(source); + Local<Value> value = script->Run(); + CHECK(value->IsNumber()); + CHECK_EQ(7, value->Int32Value()); + v8::internal::Heap::CollectAllGarbage(); + CHECK_EQ(0, TestAsciiResource::dispose_count); + } + v8::internal::Heap::CollectAllGarbage(); + CHECK_EQ(1, TestAsciiResource::dispose_count); +} + + +THREADED_TEST(ScriptMakingExternalString) { + TestResource::dispose_count = 0; + uint16_t* two_byte_source = AsciiToTwoByteString("1 + 2 * 3"); + { + v8::HandleScope scope; + LocalContext env; + Local<String> source = String::New(two_byte_source); + bool success = source->MakeExternal(new TestResource(two_byte_source)); + CHECK(success); + Local<Script> script = Script::Compile(source); + Local<Value> value = script->Run(); + CHECK(value->IsNumber()); + CHECK_EQ(7, value->Int32Value()); + v8::internal::Heap::CollectAllGarbage(); + CHECK_EQ(0, TestResource::dispose_count); + } + v8::internal::Heap::CollectAllGarbage(); + CHECK_EQ(1, TestResource::dispose_count); +} + + +THREADED_TEST(ScriptMakingExternalAsciiString) { + TestAsciiResource::dispose_count = 0; + const char* c_source = "1 + 2 * 3"; + { + v8::HandleScope scope; + LocalContext env; + Local<String> source = v8_str(c_source); + bool success = source->MakeExternal( + new TestAsciiResource(i::StrDup(c_source))); + CHECK(success); + Local<Script> script = Script::Compile(source); + Local<Value> value = script->Run(); + CHECK(value->IsNumber()); + CHECK_EQ(7, value->Int32Value()); + v8::internal::Heap::CollectAllGarbage(); + CHECK_EQ(0, TestAsciiResource::dispose_count); + } + v8::internal::Heap::CollectAllGarbage(); + CHECK_EQ(1, TestAsciiResource::dispose_count); +} + + +THREADED_TEST(UsingExternalString) { + v8::HandleScope scope; + uint16_t* two_byte_string = AsciiToTwoByteString("test string"); + Local<String> string = String::NewExternal(new TestResource(two_byte_string)); + i::Handle<i::String> istring = v8::Utils::OpenHandle(*string); + // Trigger GCs so that the newly allocated string moves to old gen. + i::Heap::CollectGarbage(0, i::NEW_SPACE); // in survivor space now + i::Heap::CollectGarbage(0, i::NEW_SPACE); // in old gen now + i::Handle<i::String> isymbol = i::Factory::SymbolFromString(istring); + CHECK(isymbol->IsSymbol()); +} + + +THREADED_TEST(UsingExternalAsciiString) { + v8::HandleScope scope; + const char* one_byte_string = "test string"; + Local<String> string = String::NewExternal( + new TestAsciiResource(i::StrDup(one_byte_string))); + i::Handle<i::String> istring = v8::Utils::OpenHandle(*string); + // Trigger GCs so that the newly allocated string moves to old gen. + i::Heap::CollectGarbage(0, i::NEW_SPACE); // in survivor space now + i::Heap::CollectGarbage(0, i::NEW_SPACE); // in old gen now + i::Handle<i::String> isymbol = i::Factory::SymbolFromString(istring); + CHECK(isymbol->IsSymbol()); +} + + +THREADED_TEST(GlobalProperties) { + v8::HandleScope scope; + LocalContext env; + v8::Handle<v8::Object> global = env->Global(); + global->Set(v8_str("pi"), v8_num(3.1415926)); + Local<Value> pi = global->Get(v8_str("pi")); + CHECK_EQ(3.1415926, pi->NumberValue()); +} + + +static v8::Handle<Value> handle_call(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + return v8_num(102); +} + + +static v8::Handle<Value> construct_call(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + args.This()->Set(v8_str("x"), v8_num(1)); + args.This()->Set(v8_str("y"), v8_num(2)); + return args.This(); +} + +THREADED_TEST(FunctionTemplate) { + v8::HandleScope scope; + LocalContext env; + { + Local<v8::FunctionTemplate> fun_templ = + v8::FunctionTemplate::New(handle_call); + Local<Function> fun = fun_templ->GetFunction(); + env->Global()->Set(v8_str("obj"), fun); + Local<Script> script = v8_compile("obj()"); + CHECK_EQ(102, script->Run()->Int32Value()); + } + // Use SetCallHandler to initialize a function template, should work like the + // previous one. + { + Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(); + fun_templ->SetCallHandler(handle_call); + Local<Function> fun = fun_templ->GetFunction(); + env->Global()->Set(v8_str("obj"), fun); + Local<Script> script = v8_compile("obj()"); + CHECK_EQ(102, script->Run()->Int32Value()); + } + // Test constructor calls. + { + Local<v8::FunctionTemplate> fun_templ = + v8::FunctionTemplate::New(construct_call); + fun_templ->SetClassName(v8_str("funky")); + Local<Function> fun = fun_templ->GetFunction(); + env->Global()->Set(v8_str("obj"), fun); + Local<Script> script = v8_compile("var s = new obj(); s.x"); + CHECK_EQ(1, script->Run()->Int32Value()); + + Local<Value> result = v8_compile("(new obj()).toString()")->Run(); + CHECK_EQ(v8_str("[object funky]"), result); + } +} + + +static v8::Handle<Value> handle_property(Local<String> name, + const AccessorInfo&) { + ApiTestFuzzer::Fuzz(); + return v8_num(900); +} + + +THREADED_TEST(PropertyHandler) { + v8::HandleScope scope; + Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(); + fun_templ->InstanceTemplate()->SetAccessor(v8_str("foo"), handle_property); + LocalContext env; + Local<Function> fun = fun_templ->GetFunction(); + env->Global()->Set(v8_str("Fun"), fun); + Local<Script> getter = v8_compile("var obj = new Fun(); obj.foo;"); + CHECK_EQ(900, getter->Run()->Int32Value()); + Local<Script> setter = v8_compile("obj.foo = 901;"); + CHECK_EQ(901, setter->Run()->Int32Value()); +} + + +THREADED_TEST(Number) { + v8::HandleScope scope; + LocalContext env; + double PI = 3.1415926; + Local<v8::Number> pi_obj = v8::Number::New(PI); + CHECK_EQ(PI, pi_obj->NumberValue()); +} + + +THREADED_TEST(ToNumber) { + v8::HandleScope scope; + LocalContext env; + Local<String> str = v8_str("3.1415926"); + CHECK_EQ(3.1415926, str->NumberValue()); + v8::Handle<v8::Boolean> t = v8::True(); + CHECK_EQ(1.0, t->NumberValue()); + v8::Handle<v8::Boolean> f = v8::False(); + CHECK_EQ(0.0, f->NumberValue()); +} + + +THREADED_TEST(Date) { + v8::HandleScope scope; + LocalContext env; + double PI = 3.1415926; + Local<Value> date_obj = v8::Date::New(PI); + CHECK_EQ(3.0, date_obj->NumberValue()); +} + + +THREADED_TEST(Boolean) { + v8::HandleScope scope; + LocalContext env; + v8::Handle<v8::Boolean> t = v8::True(); + CHECK(t->Value()); + v8::Handle<v8::Boolean> f = v8::False(); + CHECK(!f->Value()); + v8::Handle<v8::Primitive> u = v8::Undefined(); + CHECK(!u->BooleanValue()); + v8::Handle<v8::Primitive> n = v8::Null(); + CHECK(!n->BooleanValue()); + v8::Handle<String> str1 = v8_str(""); + CHECK(!str1->BooleanValue()); + v8::Handle<String> str2 = v8_str("x"); + CHECK(str2->BooleanValue()); + CHECK(!v8::Number::New(0)->BooleanValue()); + CHECK(v8::Number::New(-1)->BooleanValue()); + CHECK(v8::Number::New(1)->BooleanValue()); + CHECK(v8::Number::New(42)->BooleanValue()); + CHECK(!v8_compile("NaN")->Run()->BooleanValue()); +} + + +static v8::Handle<Value> DummyCallHandler(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + return v8_num(13.4); +} + + +static v8::Handle<Value> GetM(Local<String> name, const AccessorInfo&) { + ApiTestFuzzer::Fuzz(); + return v8_num(876); +} + + +THREADED_TEST(GlobalPrototype) { + v8::HandleScope scope; + v8::Handle<v8::FunctionTemplate> func_templ = v8::FunctionTemplate::New(); + func_templ->PrototypeTemplate()->Set( + "dummy", + v8::FunctionTemplate::New(DummyCallHandler)); + v8::Handle<ObjectTemplate> templ = func_templ->InstanceTemplate(); + templ->Set("x", v8_num(200)); + templ->SetAccessor(v8_str("m"), GetM); + LocalContext env(0, templ); + v8::Handle<v8::Object> obj = env->Global(); + v8::Handle<Script> script = v8_compile("dummy()"); + v8::Handle<Value> result = script->Run(); + CHECK_EQ(13.4, result->NumberValue()); + CHECK_EQ(200, v8_compile("x")->Run()->Int32Value()); + CHECK_EQ(876, v8_compile("m")->Run()->Int32Value()); +} + + +static v8::Handle<Value> GetIntValue(Local<String> property, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + int* value = + static_cast<int*>(v8::Handle<v8::External>::Cast(info.Data())->Value()); + return v8_num(*value); +} + +static void SetIntValue(Local<String> property, + Local<Value> value, + const AccessorInfo& info) { + int* field = + static_cast<int*>(v8::Handle<v8::External>::Cast(info.Data())->Value()); + *field = value->Int32Value(); +} + +int foo, bar, baz; + +THREADED_TEST(GlobalVariableAccess) { + foo = 0; + bar = -4; + baz = 10; + v8::HandleScope scope; + v8::Handle<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(); + templ->InstanceTemplate()->SetAccessor(v8_str("foo"), + GetIntValue, + SetIntValue, + v8::External::New(&foo)); + templ->InstanceTemplate()->SetAccessor(v8_str("bar"), + GetIntValue, + SetIntValue, + v8::External::New(&bar)); + templ->InstanceTemplate()->SetAccessor(v8_str("baz"), + GetIntValue, + SetIntValue, + v8::External::New(&baz)); + LocalContext env(0, templ->InstanceTemplate()); + v8_compile("foo = (++bar) + baz")->Run(); + CHECK_EQ(bar, -3); + CHECK_EQ(foo, 7); +} + + +THREADED_TEST(ObjectTemplate) { + v8::HandleScope scope; + Local<ObjectTemplate> templ1 = ObjectTemplate::New(); + templ1->Set("x", v8_num(10)); + templ1->Set("y", v8_num(13)); + LocalContext env; + Local<v8::Object> instance1 = templ1->NewInstance(); + env->Global()->Set(v8_str("p"), instance1); + CHECK(v8_compile("(p.x == 10)")->Run()->BooleanValue()); + CHECK(v8_compile("(p.y == 13)")->Run()->BooleanValue()); + Local<v8::FunctionTemplate> fun = v8::FunctionTemplate::New(); + fun->PrototypeTemplate()->Set("nirk", v8_num(123)); + Local<ObjectTemplate> templ2 = fun->InstanceTemplate(); + templ2->Set("a", v8_num(12)); + templ2->Set("b", templ1); + Local<v8::Object> instance2 = templ2->NewInstance(); + env->Global()->Set(v8_str("q"), instance2); + CHECK(v8_compile("(q.nirk == 123)")->Run()->BooleanValue()); + CHECK(v8_compile("(q.a == 12)")->Run()->BooleanValue()); + CHECK(v8_compile("(q.b.x == 10)")->Run()->BooleanValue()); + CHECK(v8_compile("(q.b.y == 13)")->Run()->BooleanValue()); +} + + +static v8::Handle<Value> GetFlabby(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + return v8_num(17.2); +} + + +static v8::Handle<Value> GetKnurd(Local<String> property, const AccessorInfo&) { + ApiTestFuzzer::Fuzz(); + return v8_num(15.2); +} + + +THREADED_TEST(DescriptorInheritance) { + v8::HandleScope scope; + v8::Handle<v8::FunctionTemplate> super = v8::FunctionTemplate::New(); + super->PrototypeTemplate()->Set("flabby", + v8::FunctionTemplate::New(GetFlabby)); + super->PrototypeTemplate()->Set("PI", v8_num(3.14)); + + super->InstanceTemplate()->SetAccessor(v8_str("knurd"), GetKnurd); + + v8::Handle<v8::FunctionTemplate> base1 = v8::FunctionTemplate::New(); + base1->Inherit(super); + base1->PrototypeTemplate()->Set("v1", v8_num(20.1)); + + v8::Handle<v8::FunctionTemplate> base2 = v8::FunctionTemplate::New(); + base2->Inherit(super); + base2->PrototypeTemplate()->Set("v2", v8_num(10.1)); + + LocalContext env; + + env->Global()->Set(v8_str("s"), super->GetFunction()); + env->Global()->Set(v8_str("base1"), base1->GetFunction()); + env->Global()->Set(v8_str("base2"), base2->GetFunction()); + + // Checks right __proto__ chain. + CHECK(CompileRun("base1.prototype.__proto__ == s.prototype")->BooleanValue()); + CHECK(CompileRun("base2.prototype.__proto__ == s.prototype")->BooleanValue()); + + CHECK(v8_compile("s.prototype.PI == 3.14")->Run()->BooleanValue()); + + // Instance accessor should not be visible on function object or its prototype + CHECK(CompileRun("s.knurd == undefined")->BooleanValue()); + CHECK(CompileRun("s.prototype.knurd == undefined")->BooleanValue()); + CHECK(CompileRun("base1.prototype.knurd == undefined")->BooleanValue()); + + env->Global()->Set(v8_str("obj"), + base1->GetFunction()->NewInstance()); + CHECK_EQ(17.2, v8_compile("obj.flabby()")->Run()->NumberValue()); + CHECK(v8_compile("'flabby' in obj")->Run()->BooleanValue()); + CHECK_EQ(15.2, v8_compile("obj.knurd")->Run()->NumberValue()); + CHECK(v8_compile("'knurd' in obj")->Run()->BooleanValue()); + CHECK_EQ(20.1, v8_compile("obj.v1")->Run()->NumberValue()); + + env->Global()->Set(v8_str("obj2"), + base2->GetFunction()->NewInstance()); + CHECK_EQ(17.2, v8_compile("obj2.flabby()")->Run()->NumberValue()); + CHECK(v8_compile("'flabby' in obj2")->Run()->BooleanValue()); + CHECK_EQ(15.2, v8_compile("obj2.knurd")->Run()->NumberValue()); + CHECK(v8_compile("'knurd' in obj2")->Run()->BooleanValue()); + CHECK_EQ(10.1, v8_compile("obj2.v2")->Run()->NumberValue()); + + // base1 and base2 cannot cross reference to each's prototype + CHECK(v8_compile("obj.v2")->Run()->IsUndefined()); + CHECK(v8_compile("obj2.v1")->Run()->IsUndefined()); +} + + +int echo_named_call_count; + + +static v8::Handle<Value> EchoNamedProperty(Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK_EQ(v8_str("data"), info.Data()); + echo_named_call_count++; + return name; +} + + +THREADED_TEST(NamedPropertyHandlerGetter) { + echo_named_call_count = 0; + v8::HandleScope scope; + v8::Handle<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(); + templ->InstanceTemplate()->SetNamedPropertyHandler(EchoNamedProperty, + 0, 0, 0, 0, + v8_str("data")); + LocalContext env; + env->Global()->Set(v8_str("obj"), + templ->GetFunction()->NewInstance()); + CHECK_EQ(echo_named_call_count, 0); + v8_compile("obj.x")->Run(); + CHECK_EQ(echo_named_call_count, 1); + const char* code = "var str = 'oddle'; obj[str] + obj.poddle;"; + v8::Handle<Value> str = CompileRun(code); + String::AsciiValue value(str); + CHECK_EQ(*value, "oddlepoddle"); + // Check default behavior + CHECK_EQ(v8_compile("obj.flob = 10;")->Run()->Int32Value(), 10); + CHECK(v8_compile("'myProperty' in obj")->Run()->BooleanValue()); + CHECK(v8_compile("delete obj.myProperty")->Run()->BooleanValue()); +} + + +int echo_indexed_call_count = 0; + + +static v8::Handle<Value> EchoIndexedProperty(uint32_t index, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK_EQ(v8_num(637), info.Data()); + echo_indexed_call_count++; + return v8_num(index); +} + + +THREADED_TEST(IndexedPropertyHandlerGetter) { + v8::HandleScope scope; + v8::Handle<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(); + templ->InstanceTemplate()->SetIndexedPropertyHandler(EchoIndexedProperty, + 0, 0, 0, 0, + v8_num(637)); + LocalContext env; + env->Global()->Set(v8_str("obj"), + templ->GetFunction()->NewInstance()); + Local<Script> script = v8_compile("obj[900]"); + CHECK_EQ(script->Run()->Int32Value(), 900); +} + + +v8::Handle<v8::Object> bottom; + +static v8::Handle<Value> CheckThisIndexedPropertyHandler( + uint32_t index, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.This()->Equals(bottom)); + return v8::Handle<Value>(); +} + +static v8::Handle<Value> CheckThisNamedPropertyHandler( + Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.This()->Equals(bottom)); + return v8::Handle<Value>(); +} + + +v8::Handle<Value> CheckThisIndexedPropertySetter(uint32_t index, + Local<Value> value, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.This()->Equals(bottom)); + return v8::Handle<Value>(); +} + + +v8::Handle<Value> CheckThisNamedPropertySetter(Local<String> property, + Local<Value> value, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.This()->Equals(bottom)); + return v8::Handle<Value>(); +} + +v8::Handle<v8::Boolean> CheckThisIndexedPropertyQuery( + uint32_t index, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.This()->Equals(bottom)); + return v8::Handle<v8::Boolean>(); +} + + +v8::Handle<v8::Boolean> CheckThisNamedPropertyQuery(Local<String> property, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.This()->Equals(bottom)); + return v8::Handle<v8::Boolean>(); +} + + +v8::Handle<v8::Boolean> CheckThisIndexedPropertyDeleter( + uint32_t index, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.This()->Equals(bottom)); + return v8::Handle<v8::Boolean>(); +} + + +v8::Handle<v8::Boolean> CheckThisNamedPropertyDeleter( + Local<String> property, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.This()->Equals(bottom)); + return v8::Handle<v8::Boolean>(); +} + + +v8::Handle<v8::Array> CheckThisIndexedPropertyEnumerator( + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.This()->Equals(bottom)); + return v8::Handle<v8::Array>(); +} + + +v8::Handle<v8::Array> CheckThisNamedPropertyEnumerator( + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.This()->Equals(bottom)); + return v8::Handle<v8::Array>(); +} + + +THREADED_TEST(PropertyHandlerInPrototype) { + v8::HandleScope scope; + LocalContext env; + + // Set up a prototype chain with three interceptors. + v8::Handle<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(); + templ->InstanceTemplate()->SetIndexedPropertyHandler( + CheckThisIndexedPropertyHandler, + CheckThisIndexedPropertySetter, + CheckThisIndexedPropertyQuery, + CheckThisIndexedPropertyDeleter, + CheckThisIndexedPropertyEnumerator); + + templ->InstanceTemplate()->SetNamedPropertyHandler( + CheckThisNamedPropertyHandler, + CheckThisNamedPropertySetter, + CheckThisNamedPropertyQuery, + CheckThisNamedPropertyDeleter, + CheckThisNamedPropertyEnumerator); + + bottom = templ->GetFunction()->NewInstance(); + Local<v8::Object> top = templ->GetFunction()->NewInstance(); + Local<v8::Object> middle = templ->GetFunction()->NewInstance(); + + bottom->Set(v8_str("__proto__"), middle); + middle->Set(v8_str("__proto__"), top); + env->Global()->Set(v8_str("obj"), bottom); + + // Indexed and named get. + Script::Compile(v8_str("obj[0]"))->Run(); + Script::Compile(v8_str("obj.x"))->Run(); + + // Indexed and named set. + Script::Compile(v8_str("obj[1] = 42"))->Run(); + Script::Compile(v8_str("obj.y = 42"))->Run(); + + // Indexed and named query. + Script::Compile(v8_str("0 in obj"))->Run(); + Script::Compile(v8_str("'x' in obj"))->Run(); + + // Indexed and named deleter. + Script::Compile(v8_str("delete obj[0]"))->Run(); + Script::Compile(v8_str("delete obj.x"))->Run(); + + // Enumerators. + Script::Compile(v8_str("for (var p in obj) ;"))->Run(); +} + + +static v8::Handle<Value> PrePropertyHandlerGet(Local<String> key, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + if (v8_str("pre")->Equals(key)) { + return v8_str("PrePropertyHandler: pre"); + } + return v8::Handle<String>(); +} + + +static v8::Handle<v8::Boolean> PrePropertyHandlerHas(Local<String> key, + const AccessorInfo&) { + if (v8_str("pre")->Equals(key)) { + return v8::True(); + } + + return v8::Handle<v8::Boolean>(); // do not intercept the call +} + + +THREADED_TEST(PrePropertyHandler) { + v8::HandleScope scope; + v8::Handle<v8::FunctionTemplate> desc = v8::FunctionTemplate::New(); + desc->InstanceTemplate()->SetNamedPropertyHandler(PrePropertyHandlerGet, + 0, + PrePropertyHandlerHas); + LocalContext env(NULL, desc->InstanceTemplate()); + Script::Compile(v8_str( + "var pre = 'Object: pre'; var on = 'Object: on';"))->Run(); + v8::Handle<Value> result_pre = Script::Compile(v8_str("pre"))->Run(); + CHECK_EQ(v8_str("PrePropertyHandler: pre"), result_pre); + v8::Handle<Value> result_on = Script::Compile(v8_str("on"))->Run(); + CHECK_EQ(v8_str("Object: on"), result_on); + v8::Handle<Value> result_post = Script::Compile(v8_str("post"))->Run(); + CHECK(result_post.IsEmpty()); +} + + +THREADED_TEST(UndefinedIsNotEnumerable) { + v8::HandleScope scope; + LocalContext env; + v8::Handle<Value> result = Script::Compile(v8_str( + "this.propertyIsEnumerable(undefined)"))->Run(); + CHECK(result->IsFalse()); +} + + +v8::Handle<Script> call_recursively_script; +static const int kTargetRecursionDepth = 300; // near maximum + + +static v8::Handle<Value> CallScriptRecursivelyCall(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + int depth = args.This()->Get(v8_str("depth"))->Int32Value(); + if (depth == kTargetRecursionDepth) return v8::Undefined(); + args.This()->Set(v8_str("depth"), v8::Integer::New(depth + 1)); + return call_recursively_script->Run(); +} + + +static v8::Handle<Value> CallFunctionRecursivelyCall( + const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + int depth = args.This()->Get(v8_str("depth"))->Int32Value(); + if (depth == kTargetRecursionDepth) { + printf("[depth = %d]\n", depth); + return v8::Undefined(); + } + args.This()->Set(v8_str("depth"), v8::Integer::New(depth + 1)); + v8::Handle<Value> function = + args.This()->Get(v8_str("callFunctionRecursively")); + return v8::Handle<Function>::Cast(function)->Call(args.This(), 0, NULL); +} + + +THREADED_TEST(DeepCrossLanguageRecursion) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> global = ObjectTemplate::New(); + global->Set(v8_str("callScriptRecursively"), + v8::FunctionTemplate::New(CallScriptRecursivelyCall)); + global->Set(v8_str("callFunctionRecursively"), + v8::FunctionTemplate::New(CallFunctionRecursivelyCall)); + LocalContext env(NULL, global); + + env->Global()->Set(v8_str("depth"), v8::Integer::New(0)); + call_recursively_script = v8_compile("callScriptRecursively()"); + v8::Handle<Value> result = call_recursively_script->Run(); + call_recursively_script = v8::Handle<Script>(); + + env->Global()->Set(v8_str("depth"), v8::Integer::New(0)); + Script::Compile(v8_str("callFunctionRecursively()"))->Run(); +} + + +static v8::Handle<Value> + ThrowingPropertyHandlerGet(Local<String> key, const AccessorInfo&) { + ApiTestFuzzer::Fuzz(); + return v8::ThrowException(key); +} + + +static v8::Handle<Value> ThrowingPropertyHandlerSet(Local<String> key, + Local<Value>, + const AccessorInfo&) { + v8::ThrowException(key); + return v8::Undefined(); // not the same as v8::Handle<v8::Value>() +} + + +THREADED_TEST(CallbackExceptionRegression) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New(); + obj->SetNamedPropertyHandler(ThrowingPropertyHandlerGet, + ThrowingPropertyHandlerSet); + LocalContext env; + env->Global()->Set(v8_str("obj"), obj->NewInstance()); + v8::Handle<Value> otto = Script::Compile(v8_str( + "try { with (obj) { otto; } } catch (e) { e; }"))->Run(); + CHECK_EQ(v8_str("otto"), otto); + v8::Handle<Value> netto = Script::Compile(v8_str( + "try { with (obj) { netto = 4; } } catch (e) { e; }"))->Run(); + CHECK_EQ(v8_str("netto"), netto); +} + + +static v8::Handle<Value> ThrowingGetAccessor(Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + return v8::ThrowException(v8_str("g")); +} + + +static void ThrowingSetAccessor(Local<String> name, + Local<Value> value, + const AccessorInfo& info) { + v8::ThrowException(value); +} + + +THREADED_TEST(Regress1054726) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New(); + obj->SetAccessor(v8_str("x"), + ThrowingGetAccessor, + ThrowingSetAccessor, + Local<Value>()); + + LocalContext env; + env->Global()->Set(v8_str("obj"), obj->NewInstance()); + + // Use the throwing property setter/getter in a loop to force + // the accessor ICs to be initialized. + v8::Handle<Value> result; + result = Script::Compile(v8_str( + "var result = '';" + "for (var i = 0; i < 5; i++) {" + " try { obj.x; } catch (e) { result += e; }" + "}; result"))->Run(); + CHECK_EQ(v8_str("ggggg"), result); + + result = Script::Compile(String::New( + "var result = '';" + "for (var i = 0; i < 5; i++) {" + " try { obj.x = i; } catch (e) { result += e; }" + "}; result"))->Run(); + CHECK_EQ(v8_str("01234"), result); +} + + +THREADED_TEST(FunctionPrototype) { + v8::HandleScope scope; + Local<v8::FunctionTemplate> Foo = v8::FunctionTemplate::New(); + Foo->PrototypeTemplate()->Set(v8_str("plak"), v8_num(321)); + LocalContext env; + env->Global()->Set(v8_str("Foo"), Foo->GetFunction()); + Local<Script> script = Script::Compile(v8_str("Foo.prototype.plak")); + CHECK_EQ(script->Run()->Int32Value(), 321); +} + + +THREADED_TEST(InternalFields) { + v8::HandleScope scope; + LocalContext env; + + Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(); + Local<v8::ObjectTemplate> instance_templ = templ->InstanceTemplate(); + instance_templ->SetInternalFieldCount(1); + Local<v8::Object> obj = templ->GetFunction()->NewInstance(); + CHECK_EQ(1, obj->InternalFieldCount()); + CHECK(obj->GetInternalField(0)->IsUndefined()); + obj->SetInternalField(0, v8_num(17)); + CHECK_EQ(17, obj->GetInternalField(0)->Int32Value()); +} + + +THREADED_TEST(IdentityHash) { + v8::HandleScope scope; + LocalContext env; + + // Ensure that the test starts with an fresh heap to test whether the hash + // code is based on the address. + i::Heap::CollectAllGarbage(); + Local<v8::Object> obj = v8::Object::New(); + int hash = obj->GetIdentityHash(); + int hash1 = obj->GetIdentityHash(); + CHECK_EQ(hash, hash1); + int hash2 = v8::Object::New()->GetIdentityHash(); + // Since the identity hash is essentially a random number two consecutive + // objects should not be assigned the same hash code. If the test below fails + // the random number generator should be evaluated. + CHECK_NE(hash, hash2); + i::Heap::CollectAllGarbage(); + int hash3 = v8::Object::New()->GetIdentityHash(); + // Make sure that the identity hash is not based on the initial address of + // the object alone. If the test below fails the random number generator + // should be evaluated. + CHECK_NE(hash, hash3); + int hash4 = obj->GetIdentityHash(); + CHECK_EQ(hash, hash4); +} + + +THREADED_TEST(HiddenProperties) { + v8::HandleScope scope; + LocalContext env; + + v8::Local<v8::Object> obj = v8::Object::New(); + v8::Local<v8::String> key = v8_str("api-test::hidden-key"); + v8::Local<v8::String> empty = v8_str(""); + v8::Local<v8::String> prop_name = v8_str("prop_name"); + + i::Heap::CollectAllGarbage(); + + CHECK(obj->SetHiddenValue(key, v8::Integer::New(1503))); + CHECK_EQ(1503, obj->GetHiddenValue(key)->Int32Value()); + CHECK(obj->SetHiddenValue(key, v8::Integer::New(2002))); + CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value()); + + i::Heap::CollectAllGarbage(); + + // Make sure we do not find the hidden property. + CHECK(!obj->Has(empty)); + CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value()); + CHECK(obj->Get(empty)->IsUndefined()); + CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value()); + CHECK(obj->Set(empty, v8::Integer::New(2003))); + CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value()); + CHECK_EQ(2003, obj->Get(empty)->Int32Value()); + + i::Heap::CollectAllGarbage(); + + // Add another property and delete it afterwards to force the object in + // slow case. + CHECK(obj->Set(prop_name, v8::Integer::New(2008))); + CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value()); + CHECK_EQ(2008, obj->Get(prop_name)->Int32Value()); + CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value()); + CHECK(obj->Delete(prop_name)); + CHECK_EQ(2002, obj->GetHiddenValue(key)->Int32Value()); + + i::Heap::CollectAllGarbage(); + + CHECK(obj->DeleteHiddenValue(key)); + CHECK(obj->GetHiddenValue(key).IsEmpty()); +} + + +THREADED_TEST(External) { + v8::HandleScope scope; + int x = 3; + Local<v8::External> ext = v8::External::New(&x); + LocalContext env; + env->Global()->Set(v8_str("ext"), ext); + Local<Value> reext_obj = Script::Compile(v8_str("this.ext"))->Run(); + v8::Handle<v8::External> reext = v8::Handle<v8::External>::Cast(reext_obj); + int* ptr = static_cast<int*>(reext->Value()); + CHECK_EQ(x, 3); + *ptr = 10; + CHECK_EQ(x, 10); + + // Make sure unaligned pointers are wrapped properly. + char* data = i::StrDup("0123456789"); + Local<v8::Value> zero = v8::External::Wrap(&data[0]); + Local<v8::Value> one = v8::External::Wrap(&data[1]); + Local<v8::Value> two = v8::External::Wrap(&data[2]); + Local<v8::Value> three = v8::External::Wrap(&data[3]); + + char* char_ptr = reinterpret_cast<char*>(v8::External::Unwrap(zero)); + CHECK_EQ('0', *char_ptr); + char_ptr = reinterpret_cast<char*>(v8::External::Unwrap(one)); + CHECK_EQ('1', *char_ptr); + char_ptr = reinterpret_cast<char*>(v8::External::Unwrap(two)); + CHECK_EQ('2', *char_ptr); + char_ptr = reinterpret_cast<char*>(v8::External::Unwrap(three)); + CHECK_EQ('3', *char_ptr); + i::DeleteArray(data); +} + + +THREADED_TEST(GlobalHandle) { + v8::Persistent<String> global; + { + v8::HandleScope scope; + Local<String> str = v8_str("str"); + global = v8::Persistent<String>::New(str); + } + CHECK_EQ(global->Length(), 3); + global.Dispose(); +} + + +THREADED_TEST(ScriptException) { + v8::HandleScope scope; + LocalContext env; + Local<Script> script = Script::Compile(v8_str("throw 'panama!';")); + v8::TryCatch try_catch; + Local<Value> result = script->Run(); + CHECK(result.IsEmpty()); + CHECK(try_catch.HasCaught()); + String::AsciiValue exception_value(try_catch.Exception()); + CHECK_EQ(*exception_value, "panama!"); +} + + +bool message_received; + + +static void check_message(v8::Handle<v8::Message> message, + v8::Handle<Value> data) { + CHECK_EQ(5.76, data->NumberValue()); + CHECK_EQ(6.75, message->GetScriptResourceName()->NumberValue()); + message_received = true; +} + + +THREADED_TEST(MessageHandlerData) { + message_received = false; + v8::HandleScope scope; + CHECK(!message_received); + v8::V8::AddMessageListener(check_message, v8_num(5.76)); + LocalContext context; + v8::ScriptOrigin origin = + v8::ScriptOrigin(v8_str("6.75")); + Script::Compile(v8_str("throw 'error'"), &origin)->Run(); + CHECK(message_received); + // clear out the message listener + v8::V8::RemoveMessageListeners(check_message); +} + + +THREADED_TEST(GetSetProperty) { + v8::HandleScope scope; + LocalContext context; + context->Global()->Set(v8_str("foo"), v8_num(14)); + context->Global()->Set(v8_str("12"), v8_num(92)); + context->Global()->Set(v8::Integer::New(16), v8_num(32)); + context->Global()->Set(v8_num(13), v8_num(56)); + Local<Value> foo = Script::Compile(v8_str("this.foo"))->Run(); + CHECK_EQ(14, foo->Int32Value()); + Local<Value> twelve = Script::Compile(v8_str("this[12]"))->Run(); + CHECK_EQ(92, twelve->Int32Value()); + Local<Value> sixteen = Script::Compile(v8_str("this[16]"))->Run(); + CHECK_EQ(32, sixteen->Int32Value()); + Local<Value> thirteen = Script::Compile(v8_str("this[13]"))->Run(); + CHECK_EQ(56, thirteen->Int32Value()); + CHECK_EQ(92, context->Global()->Get(v8::Integer::New(12))->Int32Value()); + CHECK_EQ(92, context->Global()->Get(v8_str("12"))->Int32Value()); + CHECK_EQ(92, context->Global()->Get(v8_num(12))->Int32Value()); + CHECK_EQ(32, context->Global()->Get(v8::Integer::New(16))->Int32Value()); + CHECK_EQ(32, context->Global()->Get(v8_str("16"))->Int32Value()); + CHECK_EQ(32, context->Global()->Get(v8_num(16))->Int32Value()); + CHECK_EQ(56, context->Global()->Get(v8::Integer::New(13))->Int32Value()); + CHECK_EQ(56, context->Global()->Get(v8_str("13"))->Int32Value()); + CHECK_EQ(56, context->Global()->Get(v8_num(13))->Int32Value()); +} + + +THREADED_TEST(PropertyAttributes) { + v8::HandleScope scope; + LocalContext context; + // read-only + Local<String> prop = v8_str("read_only"); + context->Global()->Set(prop, v8_num(7), v8::ReadOnly); + CHECK_EQ(7, context->Global()->Get(prop)->Int32Value()); + Script::Compile(v8_str("read_only = 9"))->Run(); + CHECK_EQ(7, context->Global()->Get(prop)->Int32Value()); + context->Global()->Set(prop, v8_num(10)); + CHECK_EQ(7, context->Global()->Get(prop)->Int32Value()); + // dont-delete + prop = v8_str("dont_delete"); + context->Global()->Set(prop, v8_num(13), v8::DontDelete); + CHECK_EQ(13, context->Global()->Get(prop)->Int32Value()); + Script::Compile(v8_str("delete dont_delete"))->Run(); + CHECK_EQ(13, context->Global()->Get(prop)->Int32Value()); +} + + +THREADED_TEST(Array) { + v8::HandleScope scope; + LocalContext context; + Local<v8::Array> array = v8::Array::New(); + CHECK_EQ(0, array->Length()); + CHECK(array->Get(v8::Integer::New(0))->IsUndefined()); + CHECK(!array->Has(0)); + CHECK(array->Get(v8::Integer::New(100))->IsUndefined()); + CHECK(!array->Has(100)); + array->Set(v8::Integer::New(2), v8_num(7)); + CHECK_EQ(3, array->Length()); + CHECK(!array->Has(0)); + CHECK(!array->Has(1)); + CHECK(array->Has(2)); + CHECK_EQ(7, array->Get(v8::Integer::New(2))->Int32Value()); + Local<Value> obj = Script::Compile(v8_str("[1, 2, 3]"))->Run(); + Local<v8::Array> arr = Local<v8::Array>::Cast(obj); + CHECK_EQ(3, arr->Length()); + CHECK_EQ(1, arr->Get(v8::Integer::New(0))->Int32Value()); + CHECK_EQ(2, arr->Get(v8::Integer::New(1))->Int32Value()); + CHECK_EQ(3, arr->Get(v8::Integer::New(2))->Int32Value()); +} + + +v8::Handle<Value> HandleF(const v8::Arguments& args) { + v8::HandleScope scope; + ApiTestFuzzer::Fuzz(); + Local<v8::Array> result = v8::Array::New(args.Length()); + for (int i = 0; i < args.Length(); i++) + result->Set(v8::Integer::New(i), args[i]); + return scope.Close(result); +} + + +THREADED_TEST(Vector) { + v8::HandleScope scope; + Local<ObjectTemplate> global = ObjectTemplate::New(); + global->Set(v8_str("f"), v8::FunctionTemplate::New(HandleF)); + LocalContext context(0, global); + + const char* fun = "f()"; + Local<v8::Array> a0 = + Local<v8::Array>::Cast(Script::Compile(String::New(fun))->Run()); + CHECK_EQ(0, a0->Length()); + + const char* fun2 = "f(11)"; + Local<v8::Array> a1 = + Local<v8::Array>::Cast(Script::Compile(String::New(fun2))->Run()); + CHECK_EQ(1, a1->Length()); + CHECK_EQ(11, a1->Get(v8::Integer::New(0))->Int32Value()); + + const char* fun3 = "f(12, 13)"; + Local<v8::Array> a2 = + Local<v8::Array>::Cast(Script::Compile(String::New(fun3))->Run()); + CHECK_EQ(2, a2->Length()); + CHECK_EQ(12, a2->Get(v8::Integer::New(0))->Int32Value()); + CHECK_EQ(13, a2->Get(v8::Integer::New(1))->Int32Value()); + + const char* fun4 = "f(14, 15, 16)"; + Local<v8::Array> a3 = + Local<v8::Array>::Cast(Script::Compile(String::New(fun4))->Run()); + CHECK_EQ(3, a3->Length()); + CHECK_EQ(14, a3->Get(v8::Integer::New(0))->Int32Value()); + CHECK_EQ(15, a3->Get(v8::Integer::New(1))->Int32Value()); + CHECK_EQ(16, a3->Get(v8::Integer::New(2))->Int32Value()); + + const char* fun5 = "f(17, 18, 19, 20)"; + Local<v8::Array> a4 = + Local<v8::Array>::Cast(Script::Compile(String::New(fun5))->Run()); + CHECK_EQ(4, a4->Length()); + CHECK_EQ(17, a4->Get(v8::Integer::New(0))->Int32Value()); + CHECK_EQ(18, a4->Get(v8::Integer::New(1))->Int32Value()); + CHECK_EQ(19, a4->Get(v8::Integer::New(2))->Int32Value()); + CHECK_EQ(20, a4->Get(v8::Integer::New(3))->Int32Value()); +} + + +THREADED_TEST(FunctionCall) { + v8::HandleScope scope; + LocalContext context; + CompileRun( + "function Foo() {" + " var result = [];" + " for (var i = 0; i < arguments.length; i++) {" + " result.push(arguments[i]);" + " }" + " return result;" + "}"); + Local<Function> Foo = + Local<Function>::Cast(context->Global()->Get(v8_str("Foo"))); + + v8::Handle<Value>* args0 = NULL; + Local<v8::Array> a0 = Local<v8::Array>::Cast(Foo->Call(Foo, 0, args0)); + CHECK_EQ(0, a0->Length()); + + v8::Handle<Value> args1[] = { v8_num(1.1) }; + Local<v8::Array> a1 = Local<v8::Array>::Cast(Foo->Call(Foo, 1, args1)); + CHECK_EQ(1, a1->Length()); + CHECK_EQ(1.1, a1->Get(v8::Integer::New(0))->NumberValue()); + + v8::Handle<Value> args2[] = { v8_num(2.2), + v8_num(3.3) }; + Local<v8::Array> a2 = Local<v8::Array>::Cast(Foo->Call(Foo, 2, args2)); + CHECK_EQ(2, a2->Length()); + CHECK_EQ(2.2, a2->Get(v8::Integer::New(0))->NumberValue()); + CHECK_EQ(3.3, a2->Get(v8::Integer::New(1))->NumberValue()); + + v8::Handle<Value> args3[] = { v8_num(4.4), + v8_num(5.5), + v8_num(6.6) }; + Local<v8::Array> a3 = Local<v8::Array>::Cast(Foo->Call(Foo, 3, args3)); + CHECK_EQ(3, a3->Length()); + CHECK_EQ(4.4, a3->Get(v8::Integer::New(0))->NumberValue()); + CHECK_EQ(5.5, a3->Get(v8::Integer::New(1))->NumberValue()); + CHECK_EQ(6.6, a3->Get(v8::Integer::New(2))->NumberValue()); + + v8::Handle<Value> args4[] = { v8_num(7.7), + v8_num(8.8), + v8_num(9.9), + v8_num(10.11) }; + Local<v8::Array> a4 = Local<v8::Array>::Cast(Foo->Call(Foo, 4, args4)); + CHECK_EQ(4, a4->Length()); + CHECK_EQ(7.7, a4->Get(v8::Integer::New(0))->NumberValue()); + CHECK_EQ(8.8, a4->Get(v8::Integer::New(1))->NumberValue()); + CHECK_EQ(9.9, a4->Get(v8::Integer::New(2))->NumberValue()); + CHECK_EQ(10.11, a4->Get(v8::Integer::New(3))->NumberValue()); +} + + +static const char* js_code_causing_out_of_memory = + "var a = new Array(); while(true) a.push(a);"; + + +// These tests run for a long time and prevent us from running tests +// that come after them so they cannot run in parallel. +TEST(OutOfMemory) { + // It's not possible to read a snapshot into a heap with different dimensions. + if (v8::internal::Snapshot::IsEnabled()) return; + // Set heap limits. + static const int K = 1024; + v8::ResourceConstraints constraints; + constraints.set_max_young_space_size(256 * K); + constraints.set_max_old_space_size(4 * K * K); + v8::SetResourceConstraints(&constraints); + + // Execute a script that causes out of memory. + v8::HandleScope scope; + LocalContext context; + v8::V8::IgnoreOutOfMemoryException(); + Local<Script> script = + Script::Compile(String::New(js_code_causing_out_of_memory)); + Local<Value> result = script->Run(); + + // Check for out of memory state. + CHECK(result.IsEmpty()); + CHECK(context->HasOutOfMemoryException()); +} + + +v8::Handle<Value> ProvokeOutOfMemory(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + + v8::HandleScope scope; + LocalContext context; + Local<Script> script = + Script::Compile(String::New(js_code_causing_out_of_memory)); + Local<Value> result = script->Run(); + + // Check for out of memory state. + CHECK(result.IsEmpty()); + CHECK(context->HasOutOfMemoryException()); + + return result; +} + + +TEST(OutOfMemoryNested) { + // It's not possible to read a snapshot into a heap with different dimensions. + if (v8::internal::Snapshot::IsEnabled()) return; + // Set heap limits. + static const int K = 1024; + v8::ResourceConstraints constraints; + constraints.set_max_young_space_size(256 * K); + constraints.set_max_old_space_size(4 * K * K); + v8::SetResourceConstraints(&constraints); + + v8::HandleScope scope; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->Set(v8_str("ProvokeOutOfMemory"), + v8::FunctionTemplate::New(ProvokeOutOfMemory)); + LocalContext context(0, templ); + v8::V8::IgnoreOutOfMemoryException(); + Local<Value> result = CompileRun( + "var thrown = false;" + "try {" + " ProvokeOutOfMemory();" + "} catch (e) {" + " thrown = true;" + "}"); + // Check for out of memory state. + CHECK(result.IsEmpty()); + CHECK(context->HasOutOfMemoryException()); +} + + +TEST(HugeConsStringOutOfMemory) { + // It's not possible to read a snapshot into a heap with different dimensions. + if (v8::internal::Snapshot::IsEnabled()) return; + v8::HandleScope scope; + LocalContext context; + // Set heap limits. + static const int K = 1024; + v8::ResourceConstraints constraints; + constraints.set_max_young_space_size(256 * K); + constraints.set_max_old_space_size(2 * K * K); + v8::SetResourceConstraints(&constraints); + + // Execute a script that causes out of memory. + v8::V8::IgnoreOutOfMemoryException(); + + // Build huge string. This should fail with out of memory exception. + Local<Value> result = CompileRun( + "var str = Array.prototype.join.call({length: 513}, \"A\").toUpperCase();" + "for (var i = 0; i < 21; i++) { str = str + str; }"); + + // Check for out of memory state. + CHECK(result.IsEmpty()); + CHECK(context->HasOutOfMemoryException()); +} + + +THREADED_TEST(ConstructCall) { + v8::HandleScope scope; + LocalContext context; + CompileRun( + "function Foo() {" + " var result = [];" + " for (var i = 0; i < arguments.length; i++) {" + " result.push(arguments[i]);" + " }" + " return result;" + "}"); + Local<Function> Foo = + Local<Function>::Cast(context->Global()->Get(v8_str("Foo"))); + + v8::Handle<Value>* args0 = NULL; + Local<v8::Array> a0 = Local<v8::Array>::Cast(Foo->NewInstance(0, args0)); + CHECK_EQ(0, a0->Length()); + + v8::Handle<Value> args1[] = { v8_num(1.1) }; + Local<v8::Array> a1 = Local<v8::Array>::Cast(Foo->NewInstance(1, args1)); + CHECK_EQ(1, a1->Length()); + CHECK_EQ(1.1, a1->Get(v8::Integer::New(0))->NumberValue()); + + v8::Handle<Value> args2[] = { v8_num(2.2), + v8_num(3.3) }; + Local<v8::Array> a2 = Local<v8::Array>::Cast(Foo->NewInstance(2, args2)); + CHECK_EQ(2, a2->Length()); + CHECK_EQ(2.2, a2->Get(v8::Integer::New(0))->NumberValue()); + CHECK_EQ(3.3, a2->Get(v8::Integer::New(1))->NumberValue()); + + v8::Handle<Value> args3[] = { v8_num(4.4), + v8_num(5.5), + v8_num(6.6) }; + Local<v8::Array> a3 = Local<v8::Array>::Cast(Foo->NewInstance(3, args3)); + CHECK_EQ(3, a3->Length()); + CHECK_EQ(4.4, a3->Get(v8::Integer::New(0))->NumberValue()); + CHECK_EQ(5.5, a3->Get(v8::Integer::New(1))->NumberValue()); + CHECK_EQ(6.6, a3->Get(v8::Integer::New(2))->NumberValue()); + + v8::Handle<Value> args4[] = { v8_num(7.7), + v8_num(8.8), + v8_num(9.9), + v8_num(10.11) }; + Local<v8::Array> a4 = Local<v8::Array>::Cast(Foo->NewInstance(4, args4)); + CHECK_EQ(4, a4->Length()); + CHECK_EQ(7.7, a4->Get(v8::Integer::New(0))->NumberValue()); + CHECK_EQ(8.8, a4->Get(v8::Integer::New(1))->NumberValue()); + CHECK_EQ(9.9, a4->Get(v8::Integer::New(2))->NumberValue()); + CHECK_EQ(10.11, a4->Get(v8::Integer::New(3))->NumberValue()); +} + + +static void CheckUncle(v8::TryCatch* try_catch) { + CHECK(try_catch->HasCaught()); + String::AsciiValue str_value(try_catch->Exception()); + CHECK_EQ(*str_value, "uncle?"); + try_catch->Reset(); +} + + +THREADED_TEST(ConversionException) { + v8::HandleScope scope; + LocalContext env; + CompileRun( + "function TestClass() { };" + "TestClass.prototype.toString = function () { throw 'uncle?'; };" + "var obj = new TestClass();"); + Local<Value> obj = env->Global()->Get(v8_str("obj")); + + v8::TryCatch try_catch; + + Local<Value> to_string_result = obj->ToString(); + CHECK(to_string_result.IsEmpty()); + CheckUncle(&try_catch); + + Local<Value> to_number_result = obj->ToNumber(); + CHECK(to_number_result.IsEmpty()); + CheckUncle(&try_catch); + + Local<Value> to_integer_result = obj->ToInteger(); + CHECK(to_integer_result.IsEmpty()); + CheckUncle(&try_catch); + + Local<Value> to_uint32_result = obj->ToUint32(); + CHECK(to_uint32_result.IsEmpty()); + CheckUncle(&try_catch); + + Local<Value> to_int32_result = obj->ToInt32(); + CHECK(to_int32_result.IsEmpty()); + CheckUncle(&try_catch); + + Local<Value> to_object_result = v8::Undefined()->ToObject(); + CHECK(to_object_result.IsEmpty()); + CHECK(try_catch.HasCaught()); + try_catch.Reset(); + + int32_t int32_value = obj->Int32Value(); + CHECK_EQ(0, int32_value); + CheckUncle(&try_catch); + + uint32_t uint32_value = obj->Uint32Value(); + CHECK_EQ(0, uint32_value); + CheckUncle(&try_catch); + + double number_value = obj->NumberValue(); + CHECK_NE(0, IsNaN(number_value)); + CheckUncle(&try_catch); + + int64_t integer_value = obj->IntegerValue(); + CHECK_EQ(0.0, static_cast<double>(integer_value)); + CheckUncle(&try_catch); +} + + +v8::Handle<Value> ThrowFromC(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + return v8::ThrowException(v8_str("konto")); +} + + +v8::Handle<Value> CCatcher(const v8::Arguments& args) { + if (args.Length() < 1) return v8::Boolean::New(false); + v8::HandleScope scope; + v8::TryCatch try_catch; + Local<Value> result = v8::Script::Compile(args[0]->ToString())->Run(); + CHECK(!try_catch.HasCaught() || result.IsEmpty()); + return v8::Boolean::New(try_catch.HasCaught()); +} + + +THREADED_TEST(APICatch) { + v8::HandleScope scope; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->Set(v8_str("ThrowFromC"), + v8::FunctionTemplate::New(ThrowFromC)); + LocalContext context(0, templ); + CompileRun( + "var thrown = false;" + "try {" + " ThrowFromC();" + "} catch (e) {" + " thrown = true;" + "}"); + Local<Value> thrown = context->Global()->Get(v8_str("thrown")); + CHECK(thrown->BooleanValue()); +} + + +THREADED_TEST(APIThrowTryCatch) { + v8::HandleScope scope; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->Set(v8_str("ThrowFromC"), + v8::FunctionTemplate::New(ThrowFromC)); + LocalContext context(0, templ); + v8::TryCatch try_catch; + CompileRun("ThrowFromC();"); + CHECK(try_catch.HasCaught()); +} + + +// Test that a try-finally block doesn't shadow a try-catch block +// when setting up an external handler. +// +// BUG(271): Some of the exception propagation does not work on the +// ARM simulator because the simulator separates the C++ stack and the +// JS stack. This test therefore fails on the simulator. The test is +// not threaded to allow the threading tests to run on the simulator. +TEST(TryCatchInTryFinally) { + v8::HandleScope scope; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->Set(v8_str("CCatcher"), + v8::FunctionTemplate::New(CCatcher)); + LocalContext context(0, templ); + Local<Value> result = CompileRun("try {" + " try {" + " CCatcher('throw 7;');" + " } finally {" + " }" + "} catch (e) {" + "}"); + CHECK(result->IsTrue()); +} + + +static void receive_message(v8::Handle<v8::Message> message, + v8::Handle<v8::Value> data) { + message->Get(); + message_received = true; +} + + +TEST(APIThrowMessage) { + message_received = false; + v8::HandleScope scope; + v8::V8::AddMessageListener(receive_message); + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->Set(v8_str("ThrowFromC"), + v8::FunctionTemplate::New(ThrowFromC)); + LocalContext context(0, templ); + CompileRun("ThrowFromC();"); + CHECK(message_received); + v8::V8::RemoveMessageListeners(check_message); +} + + +TEST(APIThrowMessageAndVerboseTryCatch) { + message_received = false; + v8::HandleScope scope; + v8::V8::AddMessageListener(receive_message); + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->Set(v8_str("ThrowFromC"), + v8::FunctionTemplate::New(ThrowFromC)); + LocalContext context(0, templ); + v8::TryCatch try_catch; + try_catch.SetVerbose(true); + Local<Value> result = CompileRun("ThrowFromC();"); + CHECK(try_catch.HasCaught()); + CHECK(result.IsEmpty()); + CHECK(message_received); + v8::V8::RemoveMessageListeners(check_message); +} + + +THREADED_TEST(ExternalScriptException) { + v8::HandleScope scope; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->Set(v8_str("ThrowFromC"), + v8::FunctionTemplate::New(ThrowFromC)); + LocalContext context(0, templ); + + v8::TryCatch try_catch; + Local<Script> script + = Script::Compile(v8_str("ThrowFromC(); throw 'panama';")); + Local<Value> result = script->Run(); + CHECK(result.IsEmpty()); + CHECK(try_catch.HasCaught()); + String::AsciiValue exception_value(try_catch.Exception()); + CHECK_EQ("konto", *exception_value); +} + + + +v8::Handle<Value> CThrowCountDown(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + CHECK_EQ(4, args.Length()); + int count = args[0]->Int32Value(); + int cInterval = args[2]->Int32Value(); + if (count == 0) { + return v8::ThrowException(v8_str("FromC")); + } else { + Local<v8::Object> global = Context::GetCurrent()->Global(); + Local<Value> fun = global->Get(v8_str("JSThrowCountDown")); + v8::Handle<Value> argv[] = { v8_num(count - 1), + args[1], + args[2], + args[3] }; + if (count % cInterval == 0) { + v8::TryCatch try_catch; + Local<Value> result = + v8::Handle<Function>::Cast(fun)->Call(global, 4, argv); + int expected = args[3]->Int32Value(); + if (try_catch.HasCaught()) { + CHECK_EQ(expected, count); + CHECK(result.IsEmpty()); + CHECK(!i::Top::has_scheduled_exception()); + } else { + CHECK_NE(expected, count); + } + return result; + } else { + return v8::Handle<Function>::Cast(fun)->Call(global, 4, argv); + } + } +} + + +v8::Handle<Value> JSCheck(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + CHECK_EQ(3, args.Length()); + bool equality = args[0]->BooleanValue(); + int count = args[1]->Int32Value(); + int expected = args[2]->Int32Value(); + if (equality) { + CHECK_EQ(count, expected); + } else { + CHECK_NE(count, expected); + } + return v8::Undefined(); +} + + +THREADED_TEST(EvalInTryFinally) { + v8::HandleScope scope; + LocalContext context; + v8::TryCatch try_catch; + CompileRun("(function() {" + " try {" + " eval('asldkf (*&^&*^');" + " } finally {" + " return;" + " }" + "})()"); + CHECK(!try_catch.HasCaught()); +} + + +// This test works by making a stack of alternating JavaScript and C +// activations. These activations set up exception handlers with regular +// intervals, one interval for C activations and another for JavaScript +// activations. When enough activations have been created an exception is +// thrown and we check that the right activation catches the exception and that +// no other activations do. The right activation is always the topmost one with +// a handler, regardless of whether it is in JavaScript or C. +// +// The notation used to describe a test case looks like this: +// +// *JS[4] *C[3] @JS[2] C[1] JS[0] +// +// Each entry is an activation, either JS or C. The index is the count at that +// level. Stars identify activations with exception handlers, the @ identifies +// the exception handler that should catch the exception. +// +// BUG(271): Some of the exception propagation does not work on the +// ARM simulator because the simulator separates the C++ stack and the +// JS stack. This test therefore fails on the simulator. The test is +// not threaded to allow the threading tests to run on the simulator. +TEST(ExceptionOrder) { + v8::HandleScope scope; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->Set(v8_str("check"), v8::FunctionTemplate::New(JSCheck)); + templ->Set(v8_str("CThrowCountDown"), + v8::FunctionTemplate::New(CThrowCountDown)); + LocalContext context(0, templ); + CompileRun( + "function JSThrowCountDown(count, jsInterval, cInterval, expected) {" + " if (count == 0) throw 'FromJS';" + " if (count % jsInterval == 0) {" + " try {" + " var value = CThrowCountDown(count - 1," + " jsInterval," + " cInterval," + " expected);" + " check(false, count, expected);" + " return value;" + " } catch (e) {" + " check(true, count, expected);" + " }" + " } else {" + " return CThrowCountDown(count - 1, jsInterval, cInterval, expected);" + " }" + "}"); + Local<Function> fun = + Local<Function>::Cast(context->Global()->Get(v8_str("JSThrowCountDown"))); + + const int argc = 4; + // count jsInterval cInterval expected + + // *JS[4] *C[3] @JS[2] C[1] JS[0] + v8::Handle<Value> a0[argc] = { v8_num(4), v8_num(2), v8_num(3), v8_num(2) }; + fun->Call(fun, argc, a0); + + // JS[5] *C[4] JS[3] @C[2] JS[1] C[0] + v8::Handle<Value> a1[argc] = { v8_num(5), v8_num(6), v8_num(1), v8_num(2) }; + fun->Call(fun, argc, a1); + + // JS[6] @C[5] JS[4] C[3] JS[2] C[1] JS[0] + v8::Handle<Value> a2[argc] = { v8_num(6), v8_num(7), v8_num(5), v8_num(5) }; + fun->Call(fun, argc, a2); + + // @JS[6] C[5] JS[4] C[3] JS[2] C[1] JS[0] + v8::Handle<Value> a3[argc] = { v8_num(6), v8_num(6), v8_num(7), v8_num(6) }; + fun->Call(fun, argc, a3); + + // JS[6] *C[5] @JS[4] C[3] JS[2] C[1] JS[0] + v8::Handle<Value> a4[argc] = { v8_num(6), v8_num(4), v8_num(5), v8_num(4) }; + fun->Call(fun, argc, a4); + + // JS[6] C[5] *JS[4] @C[3] JS[2] C[1] JS[0] + v8::Handle<Value> a5[argc] = { v8_num(6), v8_num(4), v8_num(3), v8_num(3) }; + fun->Call(fun, argc, a5); +} + + +v8::Handle<Value> ThrowValue(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + CHECK_EQ(1, args.Length()); + return v8::ThrowException(args[0]); +} + + +THREADED_TEST(ThrowValues) { + v8::HandleScope scope; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->Set(v8_str("Throw"), v8::FunctionTemplate::New(ThrowValue)); + LocalContext context(0, templ); + v8::Handle<v8::Array> result = v8::Handle<v8::Array>::Cast(CompileRun( + "function Run(obj) {" + " try {" + " Throw(obj);" + " } catch (e) {" + " return e;" + " }" + " return 'no exception';" + "}" + "[Run('str'), Run(1), Run(0), Run(null), Run(void 0)];")); + CHECK_EQ(5, result->Length()); + CHECK(result->Get(v8::Integer::New(0))->IsString()); + CHECK(result->Get(v8::Integer::New(1))->IsNumber()); + CHECK_EQ(1, result->Get(v8::Integer::New(1))->Int32Value()); + CHECK(result->Get(v8::Integer::New(2))->IsNumber()); + CHECK_EQ(0, result->Get(v8::Integer::New(2))->Int32Value()); + CHECK(result->Get(v8::Integer::New(3))->IsNull()); + CHECK(result->Get(v8::Integer::New(4))->IsUndefined()); +} + + +THREADED_TEST(CatchZero) { + v8::HandleScope scope; + LocalContext context; + v8::TryCatch try_catch; + CHECK(!try_catch.HasCaught()); + Script::Compile(v8_str("throw 10"))->Run(); + CHECK(try_catch.HasCaught()); + CHECK_EQ(10, try_catch.Exception()->Int32Value()); + try_catch.Reset(); + CHECK(!try_catch.HasCaught()); + Script::Compile(v8_str("throw 0"))->Run(); + CHECK(try_catch.HasCaught()); + CHECK_EQ(0, try_catch.Exception()->Int32Value()); +} + + +THREADED_TEST(CatchExceptionFromWith) { + v8::HandleScope scope; + LocalContext context; + v8::TryCatch try_catch; + CHECK(!try_catch.HasCaught()); + Script::Compile(v8_str("var o = {}; with (o) { throw 42; }"))->Run(); + CHECK(try_catch.HasCaught()); +} + + +THREADED_TEST(Equality) { + v8::HandleScope scope; + LocalContext context; + // Check that equality works at all before relying on CHECK_EQ + CHECK(v8_str("a")->Equals(v8_str("a"))); + CHECK(!v8_str("a")->Equals(v8_str("b"))); + + CHECK_EQ(v8_str("a"), v8_str("a")); + CHECK_NE(v8_str("a"), v8_str("b")); + CHECK_EQ(v8_num(1), v8_num(1)); + CHECK_EQ(v8_num(1.00), v8_num(1)); + CHECK_NE(v8_num(1), v8_num(2)); + + // Assume String is not symbol. + CHECK(v8_str("a")->StrictEquals(v8_str("a"))); + CHECK(!v8_str("a")->StrictEquals(v8_str("b"))); + CHECK(!v8_str("5")->StrictEquals(v8_num(5))); + CHECK(v8_num(1)->StrictEquals(v8_num(1))); + CHECK(!v8_num(1)->StrictEquals(v8_num(2))); + CHECK(v8_num(0)->StrictEquals(v8_num(-0))); + Local<Value> not_a_number = v8_num(i::OS::nan_value()); + CHECK(!not_a_number->StrictEquals(not_a_number)); + CHECK(v8::False()->StrictEquals(v8::False())); + CHECK(!v8::False()->StrictEquals(v8::Undefined())); + + v8::Handle<v8::Object> obj = v8::Object::New(); + v8::Persistent<v8::Object> alias = v8::Persistent<v8::Object>::New(obj); + CHECK(alias->StrictEquals(obj)); + alias.Dispose(); +} + + +THREADED_TEST(MultiRun) { + v8::HandleScope scope; + LocalContext context; + Local<Script> script = Script::Compile(v8_str("x")); + for (int i = 0; i < 10; i++) + script->Run(); +} + + +static v8::Handle<Value> GetXValue(Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK_EQ(info.Data(), v8_str("donut")); + CHECK_EQ(name, v8_str("x")); + return name; +} + + +THREADED_TEST(SimplePropertyRead) { + v8::HandleScope scope; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetAccessor(v8_str("x"), GetXValue, NULL, v8_str("donut")); + LocalContext context; + context->Global()->Set(v8_str("obj"), templ->NewInstance()); + Local<Script> script = Script::Compile(v8_str("obj.x")); + for (int i = 0; i < 10; i++) { + Local<Value> result = script->Run(); + CHECK_EQ(result, v8_str("x")); + } +} + + +v8::Persistent<Value> xValue; + + +static void SetXValue(Local<String> name, + Local<Value> value, + const AccessorInfo& info) { + CHECK_EQ(value, v8_num(4)); + CHECK_EQ(info.Data(), v8_str("donut")); + CHECK_EQ(name, v8_str("x")); + CHECK(xValue.IsEmpty()); + xValue = v8::Persistent<Value>::New(value); +} + + +THREADED_TEST(SimplePropertyWrite) { + v8::HandleScope scope; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetAccessor(v8_str("x"), GetXValue, SetXValue, v8_str("donut")); + LocalContext context; + context->Global()->Set(v8_str("obj"), templ->NewInstance()); + Local<Script> script = Script::Compile(v8_str("obj.x = 4")); + for (int i = 0; i < 10; i++) { + CHECK(xValue.IsEmpty()); + script->Run(); + CHECK_EQ(v8_num(4), xValue); + xValue.Dispose(); + xValue = v8::Persistent<Value>(); + } +} + + +static v8::Handle<Value> XPropertyGetter(Local<String> property, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(info.Data()->IsUndefined()); + return property; +} + + +THREADED_TEST(NamedInterceporPropertyRead) { + v8::HandleScope scope; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetNamedPropertyHandler(XPropertyGetter); + LocalContext context; + context->Global()->Set(v8_str("obj"), templ->NewInstance()); + Local<Script> script = Script::Compile(v8_str("obj.x")); + for (int i = 0; i < 10; i++) { + Local<Value> result = script->Run(); + CHECK_EQ(result, v8_str("x")); + } +} + +THREADED_TEST(MultiContexts) { + v8::HandleScope scope; + v8::Handle<ObjectTemplate> templ = ObjectTemplate::New(); + templ->Set(v8_str("dummy"), v8::FunctionTemplate::New(DummyCallHandler)); + + Local<String> password = v8_str("Password"); + + // Create an environment + LocalContext context0(0, templ); + context0->SetSecurityToken(password); + v8::Handle<v8::Object> global0 = context0->Global(); + global0->Set(v8_str("custom"), v8_num(1234)); + CHECK_EQ(1234, global0->Get(v8_str("custom"))->Int32Value()); + + // Create an independent environment + LocalContext context1(0, templ); + context1->SetSecurityToken(password); + v8::Handle<v8::Object> global1 = context1->Global(); + global1->Set(v8_str("custom"), v8_num(1234)); + CHECK_NE(global0, global1); + CHECK_EQ(1234, global0->Get(v8_str("custom"))->Int32Value()); + CHECK_EQ(1234, global1->Get(v8_str("custom"))->Int32Value()); + + // Now create a new context with the old global + LocalContext context2(0, templ, global1); + context2->SetSecurityToken(password); + v8::Handle<v8::Object> global2 = context2->Global(); + CHECK_EQ(global1, global2); + CHECK_EQ(0, global1->Get(v8_str("custom"))->Int32Value()); + CHECK_EQ(0, global2->Get(v8_str("custom"))->Int32Value()); +} + + +THREADED_TEST(FunctionPrototypeAcrossContexts) { + // Make sure that functions created by cloning boilerplates cannot + // communicate through their __proto__ field. + + v8::HandleScope scope; + + LocalContext env0; + v8::Handle<v8::Object> global0 = + env0->Global(); + v8::Handle<v8::Object> object0 = + v8::Handle<v8::Object>::Cast(global0->Get(v8_str("Object"))); + v8::Handle<v8::Object> tostring0 = + v8::Handle<v8::Object>::Cast(object0->Get(v8_str("toString"))); + v8::Handle<v8::Object> proto0 = + v8::Handle<v8::Object>::Cast(tostring0->Get(v8_str("__proto__"))); + proto0->Set(v8_str("custom"), v8_num(1234)); + + LocalContext env1; + v8::Handle<v8::Object> global1 = + env1->Global(); + v8::Handle<v8::Object> object1 = + v8::Handle<v8::Object>::Cast(global1->Get(v8_str("Object"))); + v8::Handle<v8::Object> tostring1 = + v8::Handle<v8::Object>::Cast(object1->Get(v8_str("toString"))); + v8::Handle<v8::Object> proto1 = + v8::Handle<v8::Object>::Cast(tostring1->Get(v8_str("__proto__"))); + CHECK(!proto1->Has(v8_str("custom"))); +} + + +THREADED_TEST(Regress892105) { + // Make sure that object and array literals created by cloning + // boilerplates cannot communicate through their __proto__ + // field. This is rather difficult to check, but we try to add stuff + // to Object.prototype and Array.prototype and create a new + // environment. This should succeed. + + v8::HandleScope scope; + + Local<String> source = v8_str("Object.prototype.obj = 1234;" + "Array.prototype.arr = 4567;" + "8901"); + + LocalContext env0; + Local<Script> script0 = Script::Compile(source); + CHECK_EQ(8901.0, script0->Run()->NumberValue()); + + LocalContext env1; + Local<Script> script1 = Script::Compile(source); + CHECK_EQ(8901.0, script1->Run()->NumberValue()); +} + + +static void ExpectString(const char* code, const char* expected) { + Local<Value> result = CompileRun(code); + CHECK(result->IsString()); + String::AsciiValue ascii(result); + CHECK_EQ(0, strcmp(*ascii, expected)); +} + + +static void ExpectBoolean(const char* code, bool expected) { + Local<Value> result = CompileRun(code); + CHECK(result->IsBoolean()); + CHECK_EQ(expected, result->BooleanValue()); +} + + +static void ExpectObject(const char* code, Local<Value> expected) { + Local<Value> result = CompileRun(code); + CHECK(result->Equals(expected)); +} + + +THREADED_TEST(UndetectableObject) { + v8::HandleScope scope; + LocalContext env; + + Local<v8::FunctionTemplate> desc = + v8::FunctionTemplate::New(0, v8::Handle<Value>()); + desc->InstanceTemplate()->MarkAsUndetectable(); // undetectable + + Local<v8::Object> obj = desc->GetFunction()->NewInstance(); + env->Global()->Set(v8_str("undetectable"), obj); + + ExpectString("undetectable.toString()", "[object Object]"); + ExpectString("typeof undetectable", "undefined"); + ExpectString("typeof(undetectable)", "undefined"); + ExpectBoolean("typeof undetectable == 'undefined'", true); + ExpectBoolean("typeof undetectable == 'object'", false); + ExpectBoolean("if (undetectable) { true; } else { false; }", false); + ExpectBoolean("!undetectable", true); + + ExpectObject("true&&undetectable", obj); + ExpectBoolean("false&&undetectable", false); + ExpectBoolean("true||undetectable", true); + ExpectObject("false||undetectable", obj); + + ExpectObject("undetectable&&true", obj); + ExpectObject("undetectable&&false", obj); + ExpectBoolean("undetectable||true", true); + ExpectBoolean("undetectable||false", false); + + ExpectBoolean("undetectable==null", true); + ExpectBoolean("null==undetectable", true); + ExpectBoolean("undetectable==undefined", true); + ExpectBoolean("undefined==undetectable", true); + ExpectBoolean("undetectable==undetectable", true); + + + ExpectBoolean("undetectable===null", false); + ExpectBoolean("null===undetectable", false); + ExpectBoolean("undetectable===undefined", false); + ExpectBoolean("undefined===undetectable", false); + ExpectBoolean("undetectable===undetectable", true); +} + + +THREADED_TEST(UndetectableString) { + v8::HandleScope scope; + LocalContext env; + + Local<String> obj = String::NewUndetectable("foo"); + env->Global()->Set(v8_str("undetectable"), obj); + + ExpectString("undetectable", "foo"); + ExpectString("typeof undetectable", "undefined"); + ExpectString("typeof(undetectable)", "undefined"); + ExpectBoolean("typeof undetectable == 'undefined'", true); + ExpectBoolean("typeof undetectable == 'string'", false); + ExpectBoolean("if (undetectable) { true; } else { false; }", false); + ExpectBoolean("!undetectable", true); + + ExpectObject("true&&undetectable", obj); + ExpectBoolean("false&&undetectable", false); + ExpectBoolean("true||undetectable", true); + ExpectObject("false||undetectable", obj); + + ExpectObject("undetectable&&true", obj); + ExpectObject("undetectable&&false", obj); + ExpectBoolean("undetectable||true", true); + ExpectBoolean("undetectable||false", false); + + ExpectBoolean("undetectable==null", true); + ExpectBoolean("null==undetectable", true); + ExpectBoolean("undetectable==undefined", true); + ExpectBoolean("undefined==undetectable", true); + ExpectBoolean("undetectable==undetectable", true); + + + ExpectBoolean("undetectable===null", false); + ExpectBoolean("null===undetectable", false); + ExpectBoolean("undetectable===undefined", false); + ExpectBoolean("undefined===undetectable", false); + ExpectBoolean("undetectable===undetectable", true); +} + + +template <typename T> static void USE(T) { } + + +// This test is not intended to be run, just type checked. +static void PersistentHandles() { + USE(PersistentHandles); + Local<String> str = v8_str("foo"); + v8::Persistent<String> p_str = v8::Persistent<String>::New(str); + USE(p_str); + Local<Script> scr = Script::Compile(v8_str("")); + v8::Persistent<Script> p_scr = v8::Persistent<Script>::New(scr); + USE(p_scr); + Local<ObjectTemplate> templ = ObjectTemplate::New(); + v8::Persistent<ObjectTemplate> p_templ = + v8::Persistent<ObjectTemplate>::New(templ); + USE(p_templ); +} + + +static v8::Handle<Value> HandleLogDelegator(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + return v8::Undefined(); +} + + +THREADED_TEST(GlobalObjectTemplate) { + v8::HandleScope handle_scope; + Local<ObjectTemplate> global_template = ObjectTemplate::New(); + global_template->Set(v8_str("JSNI_Log"), + v8::FunctionTemplate::New(HandleLogDelegator)); + v8::Persistent<Context> context = Context::New(0, global_template); + Context::Scope context_scope(context); + Script::Compile(v8_str("JSNI_Log('LOG')"))->Run(); + context.Dispose(); +} + + +static const char* kSimpleExtensionSource = + "function Foo() {" + " return 4;" + "}"; + + +THREADED_TEST(SimpleExtensions) { + v8::HandleScope handle_scope; + v8::RegisterExtension(new Extension("simpletest", kSimpleExtensionSource)); + const char* extension_names[] = { "simpletest" }; + v8::ExtensionConfiguration extensions(1, extension_names); + v8::Handle<Context> context = Context::New(&extensions); + Context::Scope lock(context); + v8::Handle<Value> result = Script::Compile(v8_str("Foo()"))->Run(); + CHECK_EQ(result, v8::Integer::New(4)); +} + + +THREADED_TEST(AutoExtensions) { + v8::HandleScope handle_scope; + Extension* extension = new Extension("autotest", kSimpleExtensionSource); + extension->set_auto_enable(true); + v8::RegisterExtension(extension); + v8::Handle<Context> context = Context::New(); + Context::Scope lock(context); + v8::Handle<Value> result = Script::Compile(v8_str("Foo()"))->Run(); + CHECK_EQ(result, v8::Integer::New(4)); +} + + +static void CheckDependencies(const char* name, const char* expected) { + v8::HandleScope handle_scope; + v8::ExtensionConfiguration config(1, &name); + LocalContext context(&config); + CHECK_EQ(String::New(expected), context->Global()->Get(v8_str("loaded"))); +} + + +/* + * Configuration: + * + * /-- B <--\ + * A <- -- D <-- E + * \-- C <--/ + */ +THREADED_TEST(ExtensionDependency) { + static const char* kEDeps[] = { "D" }; + v8::RegisterExtension(new Extension("E", "this.loaded += 'E';", 1, kEDeps)); + static const char* kDDeps[] = { "B", "C" }; + v8::RegisterExtension(new Extension("D", "this.loaded += 'D';", 2, kDDeps)); + static const char* kBCDeps[] = { "A" }; + v8::RegisterExtension(new Extension("B", "this.loaded += 'B';", 1, kBCDeps)); + v8::RegisterExtension(new Extension("C", "this.loaded += 'C';", 1, kBCDeps)); + v8::RegisterExtension(new Extension("A", "this.loaded += 'A';")); + CheckDependencies("A", "undefinedA"); + CheckDependencies("B", "undefinedAB"); + CheckDependencies("C", "undefinedAC"); + CheckDependencies("D", "undefinedABCD"); + CheckDependencies("E", "undefinedABCDE"); + v8::HandleScope handle_scope; + static const char* exts[2] = { "C", "E" }; + v8::ExtensionConfiguration config(2, exts); + LocalContext context(&config); + CHECK_EQ(v8_str("undefinedACBDE"), context->Global()->Get(v8_str("loaded"))); +} + + +static const char* kExtensionTestScript = + "native function A();" + "native function B();" + "native function C();" + "function Foo(i) {" + " if (i == 0) return A();" + " if (i == 1) return B();" + " if (i == 2) return C();" + "}"; + + +static v8::Handle<Value> CallFun(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + return args.Data(); +} + + +class FunctionExtension : public Extension { + public: + FunctionExtension() : Extension("functiontest", kExtensionTestScript) { } + virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( + v8::Handle<String> name); +}; + + +static int lookup_count = 0; +v8::Handle<v8::FunctionTemplate> FunctionExtension::GetNativeFunction( + v8::Handle<String> name) { + lookup_count++; + if (name->Equals(v8_str("A"))) { + return v8::FunctionTemplate::New(CallFun, v8::Integer::New(8)); + } else if (name->Equals(v8_str("B"))) { + return v8::FunctionTemplate::New(CallFun, v8::Integer::New(7)); + } else if (name->Equals(v8_str("C"))) { + return v8::FunctionTemplate::New(CallFun, v8::Integer::New(6)); + } else { + return v8::Handle<v8::FunctionTemplate>(); + } +} + + +THREADED_TEST(FunctionLookup) { + v8::RegisterExtension(new FunctionExtension()); + v8::HandleScope handle_scope; + static const char* exts[1] = { "functiontest" }; + v8::ExtensionConfiguration config(1, exts); + LocalContext context(&config); + CHECK_EQ(3, lookup_count); + CHECK_EQ(v8::Integer::New(8), Script::Compile(v8_str("Foo(0)"))->Run()); + CHECK_EQ(v8::Integer::New(7), Script::Compile(v8_str("Foo(1)"))->Run()); + CHECK_EQ(v8::Integer::New(6), Script::Compile(v8_str("Foo(2)"))->Run()); +} + + +static const char* last_location; +static const char* last_message; +void StoringErrorCallback(const char* location, const char* message) { + if (last_location == NULL) { + last_location = location; + last_message = message; + } +} + + +// ErrorReporting creates a circular extensions configuration and +// tests that the fatal error handler gets called. This renders V8 +// unusable and therefore this test cannot be run in parallel. +TEST(ErrorReporting) { + v8::V8::SetFatalErrorHandler(StoringErrorCallback); + static const char* aDeps[] = { "B" }; + v8::RegisterExtension(new Extension("A", "", 1, aDeps)); + static const char* bDeps[] = { "A" }; + v8::RegisterExtension(new Extension("B", "", 1, bDeps)); + last_location = NULL; + v8::ExtensionConfiguration config(1, bDeps); + v8::Handle<Context> context = Context::New(&config); + CHECK(context.IsEmpty()); + CHECK_NE(last_location, NULL); +} + + +static const char* js_code_causing_huge_string_flattening = + "var str = 'X';" + "for (var i = 0; i < 29; i++) {" + " str = str + str;" + "}" + "str.match(/X/);"; + + +void OOMCallback(const char* location, const char* message) { + exit(0); +} + + +TEST(RegexpOutOfMemory) { + // Execute a script that causes out of memory when flattening a string. + v8::HandleScope scope; + v8::V8::SetFatalErrorHandler(OOMCallback); + LocalContext context; + Local<Script> script = + Script::Compile(String::New(js_code_causing_huge_string_flattening)); + last_location = NULL; + Local<Value> result = script->Run(); + + CHECK(false); // Should not return. +} + + +static void MissingScriptInfoMessageListener(v8::Handle<v8::Message> message, + v8::Handle<Value> data) { + CHECK_EQ(v8::Undefined(), data); + CHECK(message->GetScriptResourceName()->IsUndefined()); + CHECK_EQ(v8::Undefined(), message->GetScriptResourceName()); + message->GetLineNumber(); + message->GetSourceLine(); +} + + +THREADED_TEST(ErrorWithMissingScriptInfo) { + v8::HandleScope scope; + LocalContext context; + v8::V8::AddMessageListener(MissingScriptInfoMessageListener); + Script::Compile(v8_str("throw Error()"))->Run(); + v8::V8::RemoveMessageListeners(MissingScriptInfoMessageListener); +} + + +int global_index = 0; + +class Snorkel { + public: + Snorkel() { index_ = global_index++; } + int index_; +}; + +class Whammy { + public: + Whammy() { + cursor_ = 0; + } + ~Whammy() { + script_.Dispose(); + } + v8::Handle<Script> getScript() { + if (script_.IsEmpty()) + script_ = v8::Persistent<Script>::New(v8_compile("({}).blammo")); + return Local<Script>(*script_); + } + + public: + static const int kObjectCount = 256; + int cursor_; + v8::Persistent<v8::Object> objects_[kObjectCount]; + v8::Persistent<Script> script_; +}; + +static void HandleWeakReference(v8::Persistent<v8::Value> obj, void* data) { + Snorkel* snorkel = reinterpret_cast<Snorkel*>(data); + delete snorkel; + obj.ClearWeak(); +} + +v8::Handle<Value> WhammyPropertyGetter(Local<String> name, + const AccessorInfo& info) { + Whammy* whammy = + static_cast<Whammy*>(v8::Handle<v8::External>::Cast(info.Data())->Value()); + + v8::Persistent<v8::Object> prev = whammy->objects_[whammy->cursor_]; + + v8::Handle<v8::Object> obj = v8::Object::New(); + v8::Persistent<v8::Object> global = v8::Persistent<v8::Object>::New(obj); + if (!prev.IsEmpty()) { + prev->Set(v8_str("next"), obj); + prev.MakeWeak(new Snorkel(), &HandleWeakReference); + whammy->objects_[whammy->cursor_].Clear(); + } + whammy->objects_[whammy->cursor_] = global; + whammy->cursor_ = (whammy->cursor_ + 1) % Whammy::kObjectCount; + return whammy->getScript()->Run(); +} + +THREADED_TEST(WeakReference) { + v8::HandleScope handle_scope; + v8::Handle<v8::ObjectTemplate> templ= v8::ObjectTemplate::New(); + templ->SetNamedPropertyHandler(WhammyPropertyGetter, + 0, 0, 0, 0, + v8::External::New(new Whammy())); + const char* extension_list[] = { "v8/gc" }; + v8::ExtensionConfiguration extensions(1, extension_list); + v8::Persistent<Context> context = Context::New(&extensions); + Context::Scope context_scope(context); + + v8::Handle<v8::Object> interceptor = templ->NewInstance(); + context->Global()->Set(v8_str("whammy"), interceptor); + const char* code = + "var last;" + "for (var i = 0; i < 10000; i++) {" + " var obj = whammy.length;" + " if (last) last.next = obj;" + " last = obj;" + "}" + "gc();" + "4"; + v8::Handle<Value> result = CompileRun(code); + CHECK_EQ(4.0, result->NumberValue()); + + context.Dispose(); +} + + +v8::Handle<Function> args_fun; + + +static v8::Handle<Value> ArgumentsTestCallback(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + CHECK_EQ(args_fun, args.Callee()); + CHECK_EQ(3, args.Length()); + CHECK_EQ(v8::Integer::New(1), args[0]); + CHECK_EQ(v8::Integer::New(2), args[1]); + CHECK_EQ(v8::Integer::New(3), args[2]); + CHECK_EQ(v8::Undefined(), args[3]); + v8::HandleScope scope; + i::Heap::CollectAllGarbage(); + return v8::Undefined(); +} + + +THREADED_TEST(Arguments) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> global = ObjectTemplate::New(); + global->Set(v8_str("f"), v8::FunctionTemplate::New(ArgumentsTestCallback)); + LocalContext context(NULL, global); + args_fun = v8::Handle<Function>::Cast(context->Global()->Get(v8_str("f"))); + v8_compile("f(1, 2, 3)")->Run(); +} + + +static int x_register = 0; +static v8::Handle<v8::Object> x_receiver; +static v8::Handle<v8::Object> x_holder; + + +static v8::Handle<Value> XGetter(Local<String> name, const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK_EQ(x_receiver, info.This()); + CHECK_EQ(x_holder, info.Holder()); + return v8_num(x_register); +} + + +static void XSetter(Local<String> name, + Local<Value> value, + const AccessorInfo& info) { + CHECK_EQ(x_holder, info.This()); + CHECK_EQ(x_holder, info.Holder()); + x_register = value->Int32Value(); +} + + +THREADED_TEST(AccessorIC) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New(); + obj->SetAccessor(v8_str("x"), XGetter, XSetter); + LocalContext context; + x_holder = obj->NewInstance(); + context->Global()->Set(v8_str("holder"), x_holder); + x_receiver = v8::Object::New(); + context->Global()->Set(v8_str("obj"), x_receiver); + v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(CompileRun( + "obj.__proto__ = holder;" + "var result = [];" + "for (var i = 0; i < 10; i++) {" + " holder.x = i;" + " result.push(obj.x);" + "}" + "result")); + CHECK_EQ(10, array->Length()); + for (int i = 0; i < 10; i++) { + v8::Handle<Value> entry = array->Get(v8::Integer::New(i)); + CHECK_EQ(v8::Integer::New(i), entry); + } +} + + +static v8::Handle<Value> NoBlockGetterX(Local<String> name, + const AccessorInfo&) { + return v8::Handle<Value>(); +} + + +static v8::Handle<Value> NoBlockGetterI(uint32_t index, + const AccessorInfo&) { + return v8::Handle<Value>(); +} + + +static v8::Handle<v8::Boolean> PDeleter(Local<String> name, + const AccessorInfo&) { + if (!name->Equals(v8_str("foo"))) { + return v8::Handle<v8::Boolean>(); // not intercepted + } + + return v8::False(); // intercepted, and don't delete the property +} + + +static v8::Handle<v8::Boolean> IDeleter(uint32_t index, const AccessorInfo&) { + if (index != 2) { + return v8::Handle<v8::Boolean>(); // not intercepted + } + + return v8::False(); // intercepted, and don't delete the property +} + + +THREADED_TEST(Deleter) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New(); + obj->SetNamedPropertyHandler(NoBlockGetterX, NULL, NULL, PDeleter, NULL); + obj->SetIndexedPropertyHandler(NoBlockGetterI, NULL, NULL, IDeleter, NULL); + LocalContext context; + context->Global()->Set(v8_str("k"), obj->NewInstance()); + CompileRun( + "k.foo = 'foo';" + "k.bar = 'bar';" + "k[2] = 2;" + "k[4] = 4;"); + CHECK(v8_compile("delete k.foo")->Run()->IsFalse()); + CHECK(v8_compile("delete k.bar")->Run()->IsTrue()); + + CHECK_EQ(v8_compile("k.foo")->Run(), v8_str("foo")); + CHECK(v8_compile("k.bar")->Run()->IsUndefined()); + + CHECK(v8_compile("delete k[2]")->Run()->IsFalse()); + CHECK(v8_compile("delete k[4]")->Run()->IsTrue()); + + CHECK_EQ(v8_compile("k[2]")->Run(), v8_num(2)); + CHECK(v8_compile("k[4]")->Run()->IsUndefined()); +} + + +static v8::Handle<Value> GetK(Local<String> name, const AccessorInfo&) { + ApiTestFuzzer::Fuzz(); + return v8::Undefined(); +} + + +static v8::Handle<v8::Array> NamedEnum(const AccessorInfo&) { + ApiTestFuzzer::Fuzz(); + v8::Handle<v8::Array> result = v8::Array::New(3); + result->Set(v8::Integer::New(0), v8_str("foo")); + result->Set(v8::Integer::New(1), v8_str("bar")); + result->Set(v8::Integer::New(2), v8_str("baz")); + return result; +} + + +static v8::Handle<v8::Array> IndexedEnum(const AccessorInfo&) { + ApiTestFuzzer::Fuzz(); + v8::Handle<v8::Array> result = v8::Array::New(2); + result->Set(v8::Integer::New(0), v8_str("hat")); + result->Set(v8::Integer::New(1), v8_str("gyt")); + return result; +} + + +THREADED_TEST(Enumerators) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New(); + obj->SetNamedPropertyHandler(GetK, NULL, NULL, NULL, NamedEnum); + obj->SetIndexedPropertyHandler(NULL, NULL, NULL, NULL, IndexedEnum); + LocalContext context; + context->Global()->Set(v8_str("k"), obj->NewInstance()); + v8::Handle<v8::Array> result = v8::Handle<v8::Array>::Cast(CompileRun( + "var result = [];" + "for (var prop in k) {" + " result.push(prop);" + "}" + "result")); + CHECK_EQ(5, result->Length()); + CHECK_EQ(v8_str("foo"), result->Get(v8::Integer::New(0))); + CHECK_EQ(v8_str("bar"), result->Get(v8::Integer::New(1))); + CHECK_EQ(v8_str("baz"), result->Get(v8::Integer::New(2))); + CHECK_EQ(v8_str("hat"), result->Get(v8::Integer::New(3))); + CHECK_EQ(v8_str("gyt"), result->Get(v8::Integer::New(4))); +} + + +int p_getter_count; +int p_getter_count2; + + +static v8::Handle<Value> PGetter(Local<String> name, const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + p_getter_count++; + v8::Handle<v8::Object> global = Context::GetCurrent()->Global(); + CHECK_EQ(info.Holder(), global->Get(v8_str("o1"))); + if (name->Equals(v8_str("p1"))) { + CHECK_EQ(info.This(), global->Get(v8_str("o1"))); + } else if (name->Equals(v8_str("p2"))) { + CHECK_EQ(info.This(), global->Get(v8_str("o2"))); + } else if (name->Equals(v8_str("p3"))) { + CHECK_EQ(info.This(), global->Get(v8_str("o3"))); + } else if (name->Equals(v8_str("p4"))) { + CHECK_EQ(info.This(), global->Get(v8_str("o4"))); + } + return v8::Undefined(); +} + + +static void RunHolderTest(v8::Handle<v8::ObjectTemplate> obj) { + ApiTestFuzzer::Fuzz(); + LocalContext context; + context->Global()->Set(v8_str("o1"), obj->NewInstance()); + CompileRun( + "o1.__proto__ = { };" + "var o2 = { __proto__: o1 };" + "var o3 = { __proto__: o2 };" + "var o4 = { __proto__: o3 };" + "for (var i = 0; i < 10; i++) o4.p4;" + "for (var i = 0; i < 10; i++) o3.p3;" + "for (var i = 0; i < 10; i++) o2.p2;" + "for (var i = 0; i < 10; i++) o1.p1;"); +} + + +static v8::Handle<Value> PGetter2(Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + p_getter_count2++; + v8::Handle<v8::Object> global = Context::GetCurrent()->Global(); + CHECK_EQ(info.Holder(), global->Get(v8_str("o1"))); + if (name->Equals(v8_str("p1"))) { + CHECK_EQ(info.This(), global->Get(v8_str("o1"))); + } else if (name->Equals(v8_str("p2"))) { + CHECK_EQ(info.This(), global->Get(v8_str("o2"))); + } else if (name->Equals(v8_str("p3"))) { + CHECK_EQ(info.This(), global->Get(v8_str("o3"))); + } else if (name->Equals(v8_str("p4"))) { + CHECK_EQ(info.This(), global->Get(v8_str("o4"))); + } + return v8::Undefined(); +} + + +THREADED_TEST(GetterHolders) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New(); + obj->SetAccessor(v8_str("p1"), PGetter); + obj->SetAccessor(v8_str("p2"), PGetter); + obj->SetAccessor(v8_str("p3"), PGetter); + obj->SetAccessor(v8_str("p4"), PGetter); + p_getter_count = 0; + RunHolderTest(obj); + CHECK_EQ(40, p_getter_count); +} + + +THREADED_TEST(PreInterceptorHolders) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New(); + obj->SetNamedPropertyHandler(PGetter2); + p_getter_count2 = 0; + RunHolderTest(obj); + CHECK_EQ(40, p_getter_count2); +} + + +THREADED_TEST(ObjectInstantiation) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetAccessor(v8_str("t"), PGetter2); + LocalContext context; + context->Global()->Set(v8_str("o"), templ->NewInstance()); + for (int i = 0; i < 100; i++) { + v8::HandleScope inner_scope; + v8::Handle<v8::Object> obj = templ->NewInstance(); + CHECK_NE(obj, context->Global()->Get(v8_str("o"))); + context->Global()->Set(v8_str("o2"), obj); + v8::Handle<Value> value = + Script::Compile(v8_str("o.__proto__ === o2.__proto__"))->Run(); + CHECK_EQ(v8::True(), value); + context->Global()->Set(v8_str("o"), obj); + } +} + + +THREADED_TEST(StringWrite) { + v8::HandleScope scope; + v8::Handle<String> str = v8_str("abcde"); + + char buf[100]; + int len; + + memset(buf, 0x1, sizeof(buf)); + len = str->WriteAscii(buf); + CHECK_EQ(len, 5); + CHECK_EQ(strncmp("abcde\0", buf, 6), 0); + + memset(buf, 0x1, sizeof(buf)); + len = str->WriteAscii(buf, 0, 4); + CHECK_EQ(len, 4); + CHECK_EQ(strncmp("abcd\1", buf, 5), 0); + + memset(buf, 0x1, sizeof(buf)); + len = str->WriteAscii(buf, 0, 5); + CHECK_EQ(len, 5); + CHECK_EQ(strncmp("abcde\1", buf, 6), 0); + + memset(buf, 0x1, sizeof(buf)); + len = str->WriteAscii(buf, 0, 6); + CHECK_EQ(len, 5); + CHECK_EQ(strncmp("abcde\0", buf, 6), 0); + + memset(buf, 0x1, sizeof(buf)); + len = str->WriteAscii(buf, 4, -1); + CHECK_EQ(len, 1); + CHECK_EQ(strncmp("e\0", buf, 2), 0); + + memset(buf, 0x1, sizeof(buf)); + len = str->WriteAscii(buf, 4, 6); + CHECK_EQ(len, 1); + CHECK_EQ(strncmp("e\0", buf, 2), 0); + + memset(buf, 0x1, sizeof(buf)); + len = str->WriteAscii(buf, 4, 1); + CHECK_EQ(len, 1); + CHECK_EQ(strncmp("e\1", buf, 2), 0); +} + + +THREADED_TEST(ToArrayIndex) { + v8::HandleScope scope; + LocalContext context; + + v8::Handle<String> str = v8_str("42"); + v8::Handle<v8::Uint32> index = str->ToArrayIndex(); + CHECK(!index.IsEmpty()); + CHECK_EQ(42.0, index->Uint32Value()); + str = v8_str("42asdf"); + index = str->ToArrayIndex(); + CHECK(index.IsEmpty()); + str = v8_str("-42"); + index = str->ToArrayIndex(); + CHECK(index.IsEmpty()); + str = v8_str("4294967295"); + index = str->ToArrayIndex(); + CHECK(!index.IsEmpty()); + CHECK_EQ(4294967295.0, index->Uint32Value()); + v8::Handle<v8::Number> num = v8::Number::New(1); + index = num->ToArrayIndex(); + CHECK(!index.IsEmpty()); + CHECK_EQ(1.0, index->Uint32Value()); + num = v8::Number::New(-1); + index = num->ToArrayIndex(); + CHECK(index.IsEmpty()); + v8::Handle<v8::Object> obj = v8::Object::New(); + index = obj->ToArrayIndex(); + CHECK(index.IsEmpty()); +} + + +THREADED_TEST(ErrorConstruction) { + v8::HandleScope scope; + LocalContext context; + + v8::Handle<String> foo = v8_str("foo"); + v8::Handle<String> message = v8_str("message"); + v8::Handle<Value> range_error = v8::Exception::RangeError(foo); + CHECK(range_error->IsObject()); + v8::Handle<v8::Object> range_obj(v8::Handle<v8::Object>::Cast(range_error)); + CHECK(v8::Handle<v8::Object>::Cast(range_error)->Get(message)->Equals(foo)); + v8::Handle<Value> reference_error = v8::Exception::ReferenceError(foo); + CHECK(reference_error->IsObject()); + CHECK( + v8::Handle<v8::Object>::Cast(reference_error)->Get(message)->Equals(foo)); + v8::Handle<Value> syntax_error = v8::Exception::SyntaxError(foo); + CHECK(syntax_error->IsObject()); + CHECK(v8::Handle<v8::Object>::Cast(syntax_error)->Get(message)->Equals(foo)); + v8::Handle<Value> type_error = v8::Exception::TypeError(foo); + CHECK(type_error->IsObject()); + CHECK(v8::Handle<v8::Object>::Cast(type_error)->Get(message)->Equals(foo)); + v8::Handle<Value> error = v8::Exception::Error(foo); + CHECK(error->IsObject()); + CHECK(v8::Handle<v8::Object>::Cast(error)->Get(message)->Equals(foo)); +} + + +static v8::Handle<Value> YGetter(Local<String> name, const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + return v8_num(10); +} + + +static void YSetter(Local<String> name, + Local<Value> value, + const AccessorInfo& info) { + if (info.This()->Has(name)) { + info.This()->Delete(name); + } + info.This()->Set(name, value); +} + + +THREADED_TEST(DeleteAccessor) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> obj = ObjectTemplate::New(); + obj->SetAccessor(v8_str("y"), YGetter, YSetter); + LocalContext context; + v8::Handle<v8::Object> holder = obj->NewInstance(); + context->Global()->Set(v8_str("holder"), holder); + v8::Handle<Value> result = CompileRun( + "holder.y = 11; holder.y = 12; holder.y"); + CHECK_EQ(12, result->Uint32Value()); +} + + +THREADED_TEST(TypeSwitch) { + v8::HandleScope scope; + v8::Handle<v8::FunctionTemplate> templ1 = v8::FunctionTemplate::New(); + v8::Handle<v8::FunctionTemplate> templ2 = v8::FunctionTemplate::New(); + v8::Handle<v8::FunctionTemplate> templ3 = v8::FunctionTemplate::New(); + v8::Handle<v8::FunctionTemplate> templs[3] = { templ1, templ2, templ3 }; + v8::Handle<v8::TypeSwitch> type_switch = v8::TypeSwitch::New(3, templs); + LocalContext context; + v8::Handle<v8::Object> obj0 = v8::Object::New(); + v8::Handle<v8::Object> obj1 = templ1->GetFunction()->NewInstance(); + v8::Handle<v8::Object> obj2 = templ2->GetFunction()->NewInstance(); + v8::Handle<v8::Object> obj3 = templ3->GetFunction()->NewInstance(); + for (int i = 0; i < 10; i++) { + CHECK_EQ(0, type_switch->match(obj0)); + CHECK_EQ(1, type_switch->match(obj1)); + CHECK_EQ(2, type_switch->match(obj2)); + CHECK_EQ(3, type_switch->match(obj3)); + CHECK_EQ(3, type_switch->match(obj3)); + CHECK_EQ(2, type_switch->match(obj2)); + CHECK_EQ(1, type_switch->match(obj1)); + CHECK_EQ(0, type_switch->match(obj0)); + } +} + + +// For use within the TestSecurityHandler() test. +static bool g_security_callback_result = false; +static bool NamedSecurityTestCallback(Local<v8::Object> global, + Local<Value> name, + v8::AccessType type, + Local<Value> data) { + // Always allow read access. + if (type == v8::ACCESS_GET) + return true; + + // Sometimes allow other access. + return g_security_callback_result; +} + + +static bool IndexedSecurityTestCallback(Local<v8::Object> global, + uint32_t key, + v8::AccessType type, + Local<Value> data) { + // Always allow read access. + if (type == v8::ACCESS_GET) + return true; + + // Sometimes allow other access. + return g_security_callback_result; +} + + +static int trouble_nesting = 0; +static v8::Handle<Value> TroubleCallback(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + trouble_nesting++; + + // Call a JS function that throws an uncaught exception. + Local<v8::Object> arg_this = Context::GetCurrent()->Global(); + Local<Value> trouble_callee = (trouble_nesting == 3) ? + arg_this->Get(v8_str("trouble_callee")) : + arg_this->Get(v8_str("trouble_caller")); + CHECK(trouble_callee->IsFunction()); + return Function::Cast(*trouble_callee)->Call(arg_this, 0, NULL); +} + + +static int report_count = 0; +static void ApiUncaughtExceptionTestListener(v8::Handle<v8::Message>, + v8::Handle<Value>) { + report_count++; +} + + +// Counts uncaught exceptions, but other tests running in parallel +// also have uncaught exceptions. +TEST(ApiUncaughtException) { + report_count = 0; + v8::HandleScope scope; + LocalContext env; + v8::V8::AddMessageListener(ApiUncaughtExceptionTestListener); + + Local<v8::FunctionTemplate> fun = v8::FunctionTemplate::New(TroubleCallback); + v8::Local<v8::Object> global = env->Global(); + global->Set(v8_str("trouble"), fun->GetFunction()); + + Script::Compile(v8_str("function trouble_callee() {" + " var x = null;" + " return x.foo;" + "};" + "function trouble_caller() {" + " trouble();" + "};"))->Run(); + Local<Value> trouble = global->Get(v8_str("trouble")); + CHECK(trouble->IsFunction()); + Local<Value> trouble_callee = global->Get(v8_str("trouble_callee")); + CHECK(trouble_callee->IsFunction()); + Local<Value> trouble_caller = global->Get(v8_str("trouble_caller")); + CHECK(trouble_caller->IsFunction()); + Function::Cast(*trouble_caller)->Call(global, 0, NULL); + CHECK_EQ(1, report_count); + v8::V8::RemoveMessageListeners(ApiUncaughtExceptionTestListener); +} + + +TEST(CompilationErrorUsingTryCatchHandler) { + v8::HandleScope scope; + LocalContext env; + v8::TryCatch try_catch; + Script::Compile(v8_str("This doesn't &*&@#$&*^ compile.")); + CHECK_NE(NULL, *try_catch.Exception()); + CHECK(try_catch.HasCaught()); +} + + +TEST(TryCatchFinallyUsingTryCatchHandler) { + v8::HandleScope scope; + LocalContext env; + v8::TryCatch try_catch; + Script::Compile(v8_str("try { throw ''; } catch (e) {}"))->Run(); + CHECK(!try_catch.HasCaught()); + Script::Compile(v8_str("try { throw ''; } finally {}"))->Run(); + CHECK(try_catch.HasCaught()); + try_catch.Reset(); + Script::Compile(v8_str("(function() {" + "try { throw ''; } finally { return; }" + "})()"))->Run(); + CHECK(!try_catch.HasCaught()); + Script::Compile(v8_str("(function()" + " { try { throw ''; } finally { throw 0; }" + "})()"))->Run(); + CHECK(try_catch.HasCaught()); +} + + +// SecurityHandler can't be run twice +TEST(SecurityHandler) { + v8::HandleScope scope0; + v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(); + global_template->SetAccessCheckCallbacks(NamedSecurityTestCallback, + IndexedSecurityTestCallback); + // Create an environment + v8::Persistent<Context> context0 = + Context::New(NULL, global_template); + context0->Enter(); + + v8::Handle<v8::Object> global0 = context0->Global(); + v8::Handle<Script> script0 = v8_compile("foo = 111"); + script0->Run(); + global0->Set(v8_str("0"), v8_num(999)); + v8::Handle<Value> foo0 = global0->Get(v8_str("foo")); + CHECK_EQ(111, foo0->Int32Value()); + v8::Handle<Value> z0 = global0->Get(v8_str("0")); + CHECK_EQ(999, z0->Int32Value()); + + // Create another environment, should fail security checks. + v8::HandleScope scope1; + + v8::Persistent<Context> context1 = + Context::New(NULL, global_template); + context1->Enter(); + + v8::Handle<v8::Object> global1 = context1->Global(); + global1->Set(v8_str("othercontext"), global0); + // This set will fail the security check. + v8::Handle<Script> script1 = + v8_compile("othercontext.foo = 222; othercontext[0] = 888;"); + script1->Run(); + // This read will pass the security check. + v8::Handle<Value> foo1 = global0->Get(v8_str("foo")); + CHECK_EQ(111, foo1->Int32Value()); + // This read will pass the security check. + v8::Handle<Value> z1 = global0->Get(v8_str("0")); + CHECK_EQ(999, z1->Int32Value()); + + // Create another environment, should pass security checks. + { g_security_callback_result = true; // allow security handler to pass. + v8::HandleScope scope2; + LocalContext context2; + v8::Handle<v8::Object> global2 = context2->Global(); + global2->Set(v8_str("othercontext"), global0); + v8::Handle<Script> script2 = + v8_compile("othercontext.foo = 333; othercontext[0] = 888;"); + script2->Run(); + v8::Handle<Value> foo2 = global0->Get(v8_str("foo")); + CHECK_EQ(333, foo2->Int32Value()); + v8::Handle<Value> z2 = global0->Get(v8_str("0")); + CHECK_EQ(888, z2->Int32Value()); + } + + context1->Exit(); + context1.Dispose(); + + context0->Exit(); + context0.Dispose(); +} + + +THREADED_TEST(SecurityChecks) { + v8::HandleScope handle_scope; + LocalContext env1; + v8::Persistent<Context> env2 = Context::New(); + + Local<Value> foo = v8_str("foo"); + Local<Value> bar = v8_str("bar"); + + // Set to the same domain. + env1->SetSecurityToken(foo); + + // Create a function in env1. + Script::Compile(v8_str("spy=function(){return spy;}"))->Run(); + Local<Value> spy = env1->Global()->Get(v8_str("spy")); + CHECK(spy->IsFunction()); + + // Create another function accessing global objects. + Script::Compile(v8_str("spy2=function(){return new this.Array();}"))->Run(); + Local<Value> spy2 = env1->Global()->Get(v8_str("spy2")); + CHECK(spy2->IsFunction()); + + // Switch to env2 in the same domain and invoke spy on env2. + { + env2->SetSecurityToken(foo); + // Enter env2 + Context::Scope scope_env2(env2); + Local<Value> result = Function::Cast(*spy)->Call(env2->Global(), 0, NULL); + CHECK(result->IsFunction()); + } + + { + env2->SetSecurityToken(bar); + Context::Scope scope_env2(env2); + + // Call cross_domain_call, it should throw an exception + v8::TryCatch try_catch; + Function::Cast(*spy2)->Call(env2->Global(), 0, NULL); + CHECK(try_catch.HasCaught()); + } + + env2.Dispose(); +} + + +// Regression test case for issue 1183439. +THREADED_TEST(SecurityChecksForPrototypeChain) { + v8::HandleScope scope; + LocalContext current; + v8::Persistent<Context> other = Context::New(); + + // Change context to be able to get to the Object function in the + // other context without hitting the security checks. + v8::Local<Value> other_object; + { Context::Scope scope(other); + other_object = other->Global()->Get(v8_str("Object")); + other->Global()->Set(v8_num(42), v8_num(87)); + } + + current->Global()->Set(v8_str("other"), other->Global()); + CHECK(v8_compile("other")->Run()->Equals(other->Global())); + + // Make sure the security check fails here and we get an undefined + // result instead of getting the Object function. Repeat in a loop + // to make sure to exercise the IC code. + v8::Local<Script> access_other0 = v8_compile("other.Object"); + v8::Local<Script> access_other1 = v8_compile("other[42]"); + for (int i = 0; i < 5; i++) { + CHECK(!access_other0->Run()->Equals(other_object)); + CHECK(access_other0->Run()->IsUndefined()); + CHECK(!access_other1->Run()->Equals(v8_num(87))); + CHECK(access_other1->Run()->IsUndefined()); + } + + // Create an object that has 'other' in its prototype chain and make + // sure we cannot access the Object function indirectly through + // that. Repeat in a loop to make sure to exercise the IC code. + v8_compile("function F() { };" + "F.prototype = other;" + "var f = new F();")->Run(); + v8::Local<Script> access_f0 = v8_compile("f.Object"); + v8::Local<Script> access_f1 = v8_compile("f[42]"); + for (int j = 0; j < 5; j++) { + CHECK(!access_f0->Run()->Equals(other_object)); + CHECK(access_f0->Run()->IsUndefined()); + CHECK(!access_f1->Run()->Equals(v8_num(87))); + CHECK(access_f1->Run()->IsUndefined()); + } + + // Now it gets hairy: Set the prototype for the other global object + // to be the current global object. The prototype chain for 'f' now + // goes through 'other' but ends up in the current global object. + { Context::Scope scope(other); + other->Global()->Set(v8_str("__proto__"), current->Global()); + } + // Set a named and an index property on the current global + // object. To force the lookup to go through the other global object, + // the properties must not exist in the other global object. + current->Global()->Set(v8_str("foo"), v8_num(100)); + current->Global()->Set(v8_num(99), v8_num(101)); + // Try to read the properties from f and make sure that the access + // gets stopped by the security checks on the other global object. + Local<Script> access_f2 = v8_compile("f.foo"); + Local<Script> access_f3 = v8_compile("f[99]"); + for (int k = 0; k < 5; k++) { + CHECK(!access_f2->Run()->Equals(v8_num(100))); + CHECK(access_f2->Run()->IsUndefined()); + CHECK(!access_f3->Run()->Equals(v8_num(101))); + CHECK(access_f3->Run()->IsUndefined()); + } + other.Dispose(); +} + + +THREADED_TEST(CrossDomainDelete) { + v8::HandleScope handle_scope; + LocalContext env1; + v8::Persistent<Context> env2 = Context::New(); + + Local<Value> foo = v8_str("foo"); + Local<Value> bar = v8_str("bar"); + + // Set to the same domain. + env1->SetSecurityToken(foo); + env2->SetSecurityToken(foo); + + env1->Global()->Set(v8_str("prop"), v8_num(3)); + env2->Global()->Set(v8_str("env1"), env1->Global()); + + // Change env2 to a different domain and delete env1.prop. + env2->SetSecurityToken(bar); + { + Context::Scope scope_env2(env2); + Local<Value> result = + Script::Compile(v8_str("delete env1.prop"))->Run(); + CHECK(result->IsFalse()); + } + + // Check that env1.prop still exists. + Local<Value> v = env1->Global()->Get(v8_str("prop")); + CHECK(v->IsNumber()); + CHECK_EQ(3, v->Int32Value()); + + env2.Dispose(); +} + + +THREADED_TEST(CrossDomainIsPropertyEnumerable) { + v8::HandleScope handle_scope; + LocalContext env1; + v8::Persistent<Context> env2 = Context::New(); + + Local<Value> foo = v8_str("foo"); + Local<Value> bar = v8_str("bar"); + + // Set to the same domain. + env1->SetSecurityToken(foo); + env2->SetSecurityToken(foo); + + env1->Global()->Set(v8_str("prop"), v8_num(3)); + env2->Global()->Set(v8_str("env1"), env1->Global()); + + // env1.prop is enumerable in env2. + Local<String> test = v8_str("propertyIsEnumerable.call(env1, 'prop')"); + { + Context::Scope scope_env2(env2); + Local<Value> result = Script::Compile(test)->Run(); + CHECK(result->IsTrue()); + } + + // Change env2 to a different domain and test again. + env2->SetSecurityToken(bar); + { + Context::Scope scope_env2(env2); + Local<Value> result = Script::Compile(test)->Run(); + CHECK(result->IsFalse()); + } + + env2.Dispose(); +} + + +THREADED_TEST(CrossDomainForIn) { + v8::HandleScope handle_scope; + LocalContext env1; + v8::Persistent<Context> env2 = Context::New(); + + Local<Value> foo = v8_str("foo"); + Local<Value> bar = v8_str("bar"); + + // Set to the same domain. + env1->SetSecurityToken(foo); + env2->SetSecurityToken(foo); + + env1->Global()->Set(v8_str("prop"), v8_num(3)); + env2->Global()->Set(v8_str("env1"), env1->Global()); + + // Change env2 to a different domain and set env1's global object + // as the __proto__ of an object in env2 and enumerate properties + // in for-in. It shouldn't enumerate properties on env1's global + // object. + env2->SetSecurityToken(bar); + { + Context::Scope scope_env2(env2); + Local<Value> result = + CompileRun("(function(){var obj = {'__proto__':env1};" + "for (var p in obj)" + " if (p == 'prop') return false;" + "return true;})()"); + CHECK(result->IsTrue()); + } + env2.Dispose(); +} + + +TEST(ContextDetachGlobal) { + v8::HandleScope handle_scope; + LocalContext env1; + v8::Persistent<Context> env2 = Context::New(); + + Local<v8::Object> global1 = env1->Global(); + + Local<Value> foo = v8_str("foo"); + + // Set to the same domain. + env1->SetSecurityToken(foo); + env2->SetSecurityToken(foo); + + // Enter env2 + env2->Enter(); + + // Create a function in env1 + Local<v8::Object> global2 = env2->Global(); + global2->Set(v8_str("prop"), v8::Integer::New(1)); + CompileRun("function getProp() {return prop;}"); + + env1->Global()->Set(v8_str("getProp"), + global2->Get(v8_str("getProp"))); + + // Detach env1's global, and reuse the global object of env1 + env2->Exit(); + env2->DetachGlobal(); + // env2 has a new global object. + CHECK(!env2->Global()->Equals(global2)); + + v8::Persistent<Context> env3 = + Context::New(0, v8::Handle<v8::ObjectTemplate>(), global2); + env3->SetSecurityToken(v8_str("bar")); + env3->Enter(); + + Local<v8::Object> global3 = env3->Global(); + CHECK_EQ(global2, global3); + CHECK(global3->Get(v8_str("prop"))->IsUndefined()); + CHECK(global3->Get(v8_str("getProp"))->IsUndefined()); + global3->Set(v8_str("prop"), v8::Integer::New(-1)); + global3->Set(v8_str("prop2"), v8::Integer::New(2)); + env3->Exit(); + + // Call getProp in env1, and it should return the value 1 + { + Local<Value> get_prop = global1->Get(v8_str("getProp")); + CHECK(get_prop->IsFunction()); + v8::TryCatch try_catch; + Local<Value> r = Function::Cast(*get_prop)->Call(global1, 0, NULL); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(1, r->Int32Value()); + } + + // Check that env3 is not accessible from env1 + { + Local<Value> r = global3->Get(v8_str("prop2")); + CHECK(r->IsUndefined()); + } + + env2.Dispose(); + env3.Dispose(); +} + + +static bool NamedAccessBlocker(Local<v8::Object> global, + Local<Value> name, + v8::AccessType type, + Local<Value> data) { + return Context::GetCurrent()->Global()->Equals(global); +} + + +static bool IndexedAccessBlocker(Local<v8::Object> global, + uint32_t key, + v8::AccessType type, + Local<Value> data) { + return Context::GetCurrent()->Global()->Equals(global); +} + + +static int g_echo_value = -1; +static v8::Handle<Value> EchoGetter(Local<String> name, + const AccessorInfo& info) { + return v8_num(g_echo_value); +} + + +static void EchoSetter(Local<String> name, + Local<Value> value, + const AccessorInfo&) { + if (value->IsNumber()) + g_echo_value = value->Int32Value(); +} + + +static v8::Handle<Value> UnreachableGetter(Local<String> name, + const AccessorInfo& info) { + CHECK(false); // This function should not be called.. + return v8::Undefined(); +} + + +static void UnreachableSetter(Local<String>, Local<Value>, + const AccessorInfo&) { + CHECK(false); // This function should nto be called. +} + + +THREADED_TEST(AccessControl) { + v8::HandleScope handle_scope; + v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(); + + global_template->SetAccessCheckCallbacks(NamedAccessBlocker, + IndexedAccessBlocker); + + // Add an accessor accessible by cross-domain JS code. + global_template->SetAccessor( + v8_str("accessible_prop"), + EchoGetter, EchoSetter, + v8::Handle<Value>(), + v8::AccessControl(v8::ALL_CAN_READ | v8::ALL_CAN_WRITE)); + + // Add an accessor that is not accessible by cross-domain JS code. + global_template->SetAccessor(v8_str("blocked_prop"), + UnreachableGetter, UnreachableSetter, + v8::Handle<Value>(), + v8::DEFAULT); + + // Create an environment + v8::Persistent<Context> context0 = Context::New(NULL, global_template); + context0->Enter(); + + v8::Handle<v8::Object> global0 = context0->Global(); + + v8::HandleScope scope1; + + v8::Persistent<Context> context1 = Context::New(); + context1->Enter(); + + v8::Handle<v8::Object> global1 = context1->Global(); + global1->Set(v8_str("other"), global0); + + v8::Handle<Value> value; + + // Access blocked property + value = v8_compile("other.blocked_prop = 1")->Run(); + value = v8_compile("other.blocked_prop")->Run(); + CHECK(value->IsUndefined()); + + value = v8_compile("propertyIsEnumerable.call(other, 'blocked_prop')")->Run(); + CHECK(value->IsFalse()); + + // Access accessible property + value = v8_compile("other.accessible_prop = 3")->Run(); + CHECK(value->IsNumber()); + CHECK_EQ(3, value->Int32Value()); + + value = v8_compile("other.accessible_prop")->Run(); + CHECK(value->IsNumber()); + CHECK_EQ(3, value->Int32Value()); + + value = + v8_compile("propertyIsEnumerable.call(other, 'accessible_prop')")->Run(); + CHECK(value->IsTrue()); + + // Enumeration doesn't enumerate accessors from inaccessible objects in + // the prototype chain even if the accessors are in themselves accessible. + Local<Value> result = + CompileRun("(function(){var obj = {'__proto__':other};" + "for (var p in obj)" + " if (p == 'accessible_prop' || p == 'blocked_prop') {" + " return false;" + " }" + "return true;})()"); + CHECK(result->IsTrue()); + + context1->Exit(); + context0->Exit(); + context1.Dispose(); + context0.Dispose(); +} + + +static v8::Handle<Value> ConstTenGetter(Local<String> name, + const AccessorInfo& info) { + return v8_num(10); +} + + +THREADED_TEST(CrossDomainAccessors) { + v8::HandleScope handle_scope; + + v8::Handle<v8::FunctionTemplate> func_template = v8::FunctionTemplate::New(); + + v8::Handle<v8::ObjectTemplate> global_template = + func_template->InstanceTemplate(); + + v8::Handle<v8::ObjectTemplate> proto_template = + func_template->PrototypeTemplate(); + + // Add an accessor to proto that's accessible by cross-domain JS code. + proto_template->SetAccessor(v8_str("accessible"), + ConstTenGetter, 0, + v8::Handle<Value>(), + v8::ALL_CAN_READ); + + // Add an accessor that is not accessible by cross-domain JS code. + global_template->SetAccessor(v8_str("unreachable"), + UnreachableGetter, 0, + v8::Handle<Value>(), + v8::DEFAULT); + + v8::Persistent<Context> context0 = Context::New(NULL, global_template); + context0->Enter(); + + Local<v8::Object> global = context0->Global(); + // Add a normal property that shadows 'accessible' + global->Set(v8_str("accessible"), v8_num(11)); + + // Enter a new context. + v8::HandleScope scope1; + v8::Persistent<Context> context1 = Context::New(); + context1->Enter(); + + v8::Handle<v8::Object> global1 = context1->Global(); + global1->Set(v8_str("other"), global); + + // Should return 10, instead of 11 + v8::Handle<Value> value = v8_compile("other.accessible")->Run(); + CHECK(value->IsNumber()); + CHECK_EQ(10, value->Int32Value()); + + value = v8_compile("other.unreachable")->Run(); + CHECK(value->IsUndefined()); + + context1->Exit(); + context0->Exit(); + context1.Dispose(); + context0.Dispose(); +} + + +static int named_access_count = 0; +static int indexed_access_count = 0; + +static bool NamedAccessCounter(Local<v8::Object> global, + Local<Value> name, + v8::AccessType type, + Local<Value> data) { + named_access_count++; + return true; +} + + +static bool IndexedAccessCounter(Local<v8::Object> global, + uint32_t key, + v8::AccessType type, + Local<Value> data) { + indexed_access_count++; + return true; +} + + +// This one is too easily disturbed by other tests. +TEST(AccessControlIC) { + named_access_count = 0; + indexed_access_count = 0; + + v8::HandleScope handle_scope; + + // Create an environment. + v8::Persistent<Context> context0 = Context::New(); + context0->Enter(); + + // Create an object that requires access-check functions to be + // called for cross-domain access. + v8::Handle<v8::ObjectTemplate> object_template = v8::ObjectTemplate::New(); + object_template->SetAccessCheckCallbacks(NamedAccessCounter, + IndexedAccessCounter); + Local<v8::Object> object = object_template->NewInstance(); + + v8::HandleScope scope1; + + // Create another environment. + v8::Persistent<Context> context1 = Context::New(); + context1->Enter(); + + // Make easy access to the object from the other environment. + v8::Handle<v8::Object> global1 = context1->Global(); + global1->Set(v8_str("obj"), object); + + v8::Handle<Value> value; + + // Check that the named access-control function is called every time. + CompileRun("function testProp(obj) {" + " for (var i = 0; i < 10; i++) obj.prop = 1;" + " for (var j = 0; j < 10; j++) obj.prop;" + " return obj.prop" + "}"); + value = CompileRun("testProp(obj)"); + CHECK(value->IsNumber()); + CHECK_EQ(1, value->Int32Value()); + CHECK_EQ(21, named_access_count); + + // Check that the named access-control function is called every time. + CompileRun("var p = 'prop';" + "function testKeyed(obj) {" + " for (var i = 0; i < 10; i++) obj[p] = 1;" + " for (var j = 0; j < 10; j++) obj[p];" + " return obj[p];" + "}"); + // Use obj which requires access checks. No inline caching is used + // in that case. + value = CompileRun("testKeyed(obj)"); + CHECK(value->IsNumber()); + CHECK_EQ(1, value->Int32Value()); + CHECK_EQ(42, named_access_count); + // Force the inline caches into generic state and try again. + CompileRun("testKeyed({ a: 0 })"); + CompileRun("testKeyed({ b: 0 })"); + value = CompileRun("testKeyed(obj)"); + CHECK(value->IsNumber()); + CHECK_EQ(1, value->Int32Value()); + CHECK_EQ(63, named_access_count); + + // Check that the indexed access-control function is called every time. + CompileRun("function testIndexed(obj) {" + " for (var i = 0; i < 10; i++) obj[0] = 1;" + " for (var j = 0; j < 10; j++) obj[0];" + " return obj[0]" + "}"); + value = CompileRun("testIndexed(obj)"); + CHECK(value->IsNumber()); + CHECK_EQ(1, value->Int32Value()); + CHECK_EQ(21, indexed_access_count); + // Force the inline caches into generic state. + CompileRun("testIndexed(new Array(1))"); + // Test that the indexed access check is called. + value = CompileRun("testIndexed(obj)"); + CHECK(value->IsNumber()); + CHECK_EQ(1, value->Int32Value()); + CHECK_EQ(42, indexed_access_count); + + // Check that the named access check is called when invoking + // functions on an object that requires access checks. + CompileRun("obj.f = function() {}"); + CompileRun("function testCallNormal(obj) {" + " for (var i = 0; i < 10; i++) obj.f();" + "}"); + CompileRun("testCallNormal(obj)"); + CHECK_EQ(74, named_access_count); + + // Force obj into slow case. + value = CompileRun("delete obj.prop"); + CHECK(value->BooleanValue()); + // Force inline caches into dictionary probing mode. + CompileRun("var o = { x: 0 }; delete o.x; testProp(o);"); + // Test that the named access check is called. + value = CompileRun("testProp(obj);"); + CHECK(value->IsNumber()); + CHECK_EQ(1, value->Int32Value()); + CHECK_EQ(96, named_access_count); + + // Force the call inline cache into dictionary probing mode. + CompileRun("o.f = function() {}; testCallNormal(o)"); + // Test that the named access check is still called for each + // invocation of the function. + value = CompileRun("testCallNormal(obj)"); + CHECK_EQ(106, named_access_count); + + context1->Exit(); + context0->Exit(); + context1.Dispose(); + context0.Dispose(); +} + + +static bool NamedAccessFlatten(Local<v8::Object> global, + Local<Value> name, + v8::AccessType type, + Local<Value> data) { + char buf[100]; + int len; + + CHECK(name->IsString()); + + memset(buf, 0x1, sizeof(buf)); + len = Local<String>::Cast(name)->WriteAscii(buf); + CHECK_EQ(4, len); + + uint16_t buf2[100]; + + memset(buf, 0x1, sizeof(buf)); + len = Local<String>::Cast(name)->Write(buf2); + CHECK_EQ(4, len); + + return true; +} + + +static bool IndexedAccessFlatten(Local<v8::Object> global, + uint32_t key, + v8::AccessType type, + Local<Value> data) { + return true; +} + + +// Regression test. In access checks, operations that may cause +// garbage collection are not allowed. It used to be the case that +// using the Write operation on a string could cause a garbage +// collection due to flattening of the string. This is no longer the +// case. +THREADED_TEST(AccessControlFlatten) { + named_access_count = 0; + indexed_access_count = 0; + + v8::HandleScope handle_scope; + + // Create an environment. + v8::Persistent<Context> context0 = Context::New(); + context0->Enter(); + + // Create an object that requires access-check functions to be + // called for cross-domain access. + v8::Handle<v8::ObjectTemplate> object_template = v8::ObjectTemplate::New(); + object_template->SetAccessCheckCallbacks(NamedAccessFlatten, + IndexedAccessFlatten); + Local<v8::Object> object = object_template->NewInstance(); + + v8::HandleScope scope1; + + // Create another environment. + v8::Persistent<Context> context1 = Context::New(); + context1->Enter(); + + // Make easy access to the object from the other environment. + v8::Handle<v8::Object> global1 = context1->Global(); + global1->Set(v8_str("obj"), object); + + v8::Handle<Value> value; + + value = v8_compile("var p = 'as' + 'df';")->Run(); + value = v8_compile("obj[p];")->Run(); + + context1->Exit(); + context0->Exit(); + context1.Dispose(); + context0.Dispose(); +} + + +static v8::Handle<Value> AccessControlNamedGetter( + Local<String>, const AccessorInfo&) { + return v8::Integer::New(42); +} + + +static v8::Handle<Value> AccessControlNamedSetter( + Local<String>, Local<Value> value, const AccessorInfo&) { + return value; +} + + +static v8::Handle<Value> AccessControlIndexedGetter( + uint32_t index, + const AccessorInfo& info) { + return v8_num(42); +} + + +static v8::Handle<Value> AccessControlIndexedSetter( + uint32_t, Local<Value> value, const AccessorInfo&) { + return value; +} + + +THREADED_TEST(AccessControlInterceptorIC) { + named_access_count = 0; + indexed_access_count = 0; + + v8::HandleScope handle_scope; + + // Create an environment. + v8::Persistent<Context> context0 = Context::New(); + context0->Enter(); + + // Create an object that requires access-check functions to be + // called for cross-domain access. The object also has interceptors + // interceptor. + v8::Handle<v8::ObjectTemplate> object_template = v8::ObjectTemplate::New(); + object_template->SetAccessCheckCallbacks(NamedAccessCounter, + IndexedAccessCounter); + object_template->SetNamedPropertyHandler(AccessControlNamedGetter, + AccessControlNamedSetter); + object_template->SetIndexedPropertyHandler(AccessControlIndexedGetter, + AccessControlIndexedSetter); + Local<v8::Object> object = object_template->NewInstance(); + + v8::HandleScope scope1; + + // Create another environment. + v8::Persistent<Context> context1 = Context::New(); + context1->Enter(); + + // Make easy access to the object from the other environment. + v8::Handle<v8::Object> global1 = context1->Global(); + global1->Set(v8_str("obj"), object); + + v8::Handle<Value> value; + + // Check that the named access-control function is called every time + // eventhough there is an interceptor on the object. + value = v8_compile("for (var i = 0; i < 10; i++) obj.x = 1;")->Run(); + value = v8_compile("for (var i = 0; i < 10; i++) obj.x;" + "obj.x")->Run(); + CHECK(value->IsNumber()); + CHECK_EQ(42, value->Int32Value()); + CHECK_EQ(21, named_access_count); + + value = v8_compile("var p = 'x';")->Run(); + value = v8_compile("for (var i = 0; i < 10; i++) obj[p] = 1;")->Run(); + value = v8_compile("for (var i = 0; i < 10; i++) obj[p];" + "obj[p]")->Run(); + CHECK(value->IsNumber()); + CHECK_EQ(42, value->Int32Value()); + CHECK_EQ(42, named_access_count); + + // Check that the indexed access-control function is called every + // time eventhough there is an interceptor on the object. + value = v8_compile("for (var i = 0; i < 10; i++) obj[0] = 1;")->Run(); + value = v8_compile("for (var i = 0; i < 10; i++) obj[0];" + "obj[0]")->Run(); + CHECK(value->IsNumber()); + CHECK_EQ(42, value->Int32Value()); + CHECK_EQ(21, indexed_access_count); + + context1->Exit(); + context0->Exit(); + context1.Dispose(); + context0.Dispose(); +} + + +THREADED_TEST(Version) { + v8::V8::GetVersion(); +} + + +static v8::Handle<Value> InstanceFunctionCallback(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + return v8_num(12); +} + + +THREADED_TEST(InstanceProperties) { + v8::HandleScope handle_scope; + LocalContext context; + + Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(); + Local<ObjectTemplate> instance = t->InstanceTemplate(); + + instance->Set(v8_str("x"), v8_num(42)); + instance->Set(v8_str("f"), + v8::FunctionTemplate::New(InstanceFunctionCallback)); + + Local<Value> o = t->GetFunction()->NewInstance(); + + context->Global()->Set(v8_str("i"), o); + Local<Value> value = Script::Compile(v8_str("i.x"))->Run(); + CHECK_EQ(42, value->Int32Value()); + + value = Script::Compile(v8_str("i.f()"))->Run(); + CHECK_EQ(12, value->Int32Value()); +} + + +static v8::Handle<Value> +GlobalObjectInstancePropertiesGet(Local<String> key, const AccessorInfo&) { + ApiTestFuzzer::Fuzz(); + return v8::Handle<Value>(); +} + + +THREADED_TEST(GlobalObjectInstanceProperties) { + v8::HandleScope handle_scope; + + Local<Value> global_object; + + Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(); + t->InstanceTemplate()->SetNamedPropertyHandler( + GlobalObjectInstancePropertiesGet); + Local<ObjectTemplate> instance_template = t->InstanceTemplate(); + instance_template->Set(v8_str("x"), v8_num(42)); + instance_template->Set(v8_str("f"), + v8::FunctionTemplate::New(InstanceFunctionCallback)); + + { + LocalContext env(NULL, instance_template); + // Hold on to the global object so it can be used again in another + // environment initialization. + global_object = env->Global(); + + Local<Value> value = Script::Compile(v8_str("x"))->Run(); + CHECK_EQ(42, value->Int32Value()); + value = Script::Compile(v8_str("f()"))->Run(); + CHECK_EQ(12, value->Int32Value()); + } + + { + // Create new environment reusing the global object. + LocalContext env(NULL, instance_template, global_object); + Local<Value> value = Script::Compile(v8_str("x"))->Run(); + CHECK_EQ(42, value->Int32Value()); + value = Script::Compile(v8_str("f()"))->Run(); + CHECK_EQ(12, value->Int32Value()); + } +} + + +static v8::Handle<Value> ShadowFunctionCallback(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + return v8_num(42); +} + + +static int shadow_y; +static int shadow_y_setter_call_count; +static int shadow_y_getter_call_count; + + +static void ShadowYSetter(Local<String>, Local<Value>, const AccessorInfo&) { + shadow_y_setter_call_count++; + shadow_y = 42; +} + + +static v8::Handle<Value> ShadowYGetter(Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + shadow_y_getter_call_count++; + return v8_num(shadow_y); +} + + +static v8::Handle<Value> ShadowIndexedGet(uint32_t index, + const AccessorInfo& info) { + return v8::Handle<Value>(); +} + + +static v8::Handle<Value> ShadowNamedGet(Local<String> key, + const AccessorInfo&) { + return v8::Handle<Value>(); +} + + +THREADED_TEST(ShadowObject) { + shadow_y = shadow_y_setter_call_count = shadow_y_getter_call_count = 0; + v8::HandleScope handle_scope; + + Local<ObjectTemplate> global_template = v8::ObjectTemplate::New(); + LocalContext context(NULL, global_template); + + Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(); + t->InstanceTemplate()->SetNamedPropertyHandler(ShadowNamedGet); + t->InstanceTemplate()->SetIndexedPropertyHandler(ShadowIndexedGet); + Local<ObjectTemplate> proto = t->PrototypeTemplate(); + Local<ObjectTemplate> instance = t->InstanceTemplate(); + + // Only allow calls of f on instances of t. + Local<v8::Signature> signature = v8::Signature::New(t); + proto->Set(v8_str("f"), + v8::FunctionTemplate::New(ShadowFunctionCallback, + Local<Value>(), + signature)); + proto->Set(v8_str("x"), v8_num(12)); + + instance->SetAccessor(v8_str("y"), ShadowYGetter, ShadowYSetter); + + Local<Value> o = t->GetFunction()->NewInstance(); + context->Global()->Set(v8_str("__proto__"), o); + + Local<Value> value = + Script::Compile(v8_str("propertyIsEnumerable(0)"))->Run(); + CHECK(value->IsBoolean()); + CHECK(!value->BooleanValue()); + + value = Script::Compile(v8_str("x"))->Run(); + CHECK_EQ(12, value->Int32Value()); + + value = Script::Compile(v8_str("f()"))->Run(); + CHECK_EQ(42, value->Int32Value()); + + Script::Compile(v8_str("y = 42"))->Run(); + CHECK_EQ(1, shadow_y_setter_call_count); + value = Script::Compile(v8_str("y"))->Run(); + CHECK_EQ(1, shadow_y_getter_call_count); + CHECK_EQ(42, value->Int32Value()); +} + + +THREADED_TEST(HiddenPrototype) { + v8::HandleScope handle_scope; + LocalContext context; + + Local<v8::FunctionTemplate> t0 = v8::FunctionTemplate::New(); + t0->InstanceTemplate()->Set(v8_str("x"), v8_num(0)); + Local<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New(); + t1->SetHiddenPrototype(true); + t1->InstanceTemplate()->Set(v8_str("y"), v8_num(1)); + Local<v8::FunctionTemplate> t2 = v8::FunctionTemplate::New(); + t2->SetHiddenPrototype(true); + t2->InstanceTemplate()->Set(v8_str("z"), v8_num(2)); + Local<v8::FunctionTemplate> t3 = v8::FunctionTemplate::New(); + t3->InstanceTemplate()->Set(v8_str("u"), v8_num(3)); + + Local<v8::Object> o0 = t0->GetFunction()->NewInstance(); + Local<v8::Object> o1 = t1->GetFunction()->NewInstance(); + Local<v8::Object> o2 = t2->GetFunction()->NewInstance(); + Local<v8::Object> o3 = t3->GetFunction()->NewInstance(); + + // Setting the prototype on an object skips hidden prototypes. + CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value()); + o0->Set(v8_str("__proto__"), o1); + CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value()); + CHECK_EQ(1, o0->Get(v8_str("y"))->Int32Value()); + o0->Set(v8_str("__proto__"), o2); + CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value()); + CHECK_EQ(1, o0->Get(v8_str("y"))->Int32Value()); + CHECK_EQ(2, o0->Get(v8_str("z"))->Int32Value()); + o0->Set(v8_str("__proto__"), o3); + CHECK_EQ(0, o0->Get(v8_str("x"))->Int32Value()); + CHECK_EQ(1, o0->Get(v8_str("y"))->Int32Value()); + CHECK_EQ(2, o0->Get(v8_str("z"))->Int32Value()); + CHECK_EQ(3, o0->Get(v8_str("u"))->Int32Value()); + + // Getting the prototype of o0 should get the first visible one + // which is o3. Therefore, z should not be defined on the prototype + // object. + Local<Value> proto = o0->Get(v8_str("__proto__")); + CHECK(proto->IsObject()); + CHECK(Local<v8::Object>::Cast(proto)->Get(v8_str("z"))->IsUndefined()); +} + + +THREADED_TEST(GetterSetterExceptions) { + v8::HandleScope handle_scope; + LocalContext context; + CompileRun( + "function Foo() { };" + "function Throw() { throw 5; };" + "var x = { };" + "x.__defineSetter__('set', Throw);" + "x.__defineGetter__('get', Throw);"); + Local<v8::Object> x = + Local<v8::Object>::Cast(context->Global()->Get(v8_str("x"))); + v8::TryCatch try_catch; + x->Set(v8_str("set"), v8::Integer::New(8)); + x->Get(v8_str("get")); + x->Set(v8_str("set"), v8::Integer::New(8)); + x->Get(v8_str("get")); + x->Set(v8_str("set"), v8::Integer::New(8)); + x->Get(v8_str("get")); + x->Set(v8_str("set"), v8::Integer::New(8)); + x->Get(v8_str("get")); +} + + +THREADED_TEST(Constructor) { + v8::HandleScope handle_scope; + LocalContext context; + Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(); + templ->SetClassName(v8_str("Fun")); + Local<Function> cons = templ->GetFunction(); + context->Global()->Set(v8_str("Fun"), cons); + Local<v8::Object> inst = cons->NewInstance(); + i::Handle<i::JSObject> obj = v8::Utils::OpenHandle(*inst); + Local<Value> value = CompileRun("(new Fun()).constructor === Fun"); + CHECK(value->BooleanValue()); +} + +THREADED_TEST(FunctionDescriptorException) { + v8::HandleScope handle_scope; + LocalContext context; + Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(); + templ->SetClassName(v8_str("Fun")); + Local<Function> cons = templ->GetFunction(); + context->Global()->Set(v8_str("Fun"), cons); + Local<Value> value = CompileRun( + "function test() {" + " try {" + " (new Fun()).blah()" + " } catch (e) {" + " var str = String(e);" + " if (str.indexOf('TypeError') == -1) return 1;" + " if (str.indexOf('[object Fun]') != -1) return 2;" + " if (str.indexOf('#<a Fun>') == -1) return 3;" + " return 0;" + " }" + " return 4;" + "}" + "test();"); + CHECK_EQ(0, value->Int32Value()); +} + + +THREADED_TEST(EvalAliasedDynamic) { + v8::HandleScope scope; + LocalContext current; + + // This sets 'global' to the real global object (as opposed to the + // proxy). It is highly implementation dependent, so take care. + current->Global()->Set(v8_str("global"), current->Global()->GetPrototype()); + + // Tests where aliased eval can only be resolved dynamically. + Local<Script> script = + Script::Compile(v8_str("function f(x) { " + " var foo = 2;" + " with (x) { return eval('foo'); }" + "}" + "foo = 0;" + "result1 = f(new Object());" + "result2 = f(global);" + "var x = new Object();" + "x.eval = function(x) { return 1; };" + "result3 = f(x);")); + script->Run(); + CHECK_EQ(2, current->Global()->Get(v8_str("result1"))->Int32Value()); + CHECK_EQ(0, current->Global()->Get(v8_str("result2"))->Int32Value()); + CHECK_EQ(1, current->Global()->Get(v8_str("result3"))->Int32Value()); + + v8::TryCatch try_catch; + script = + Script::Compile(v8_str("function f(x) { " + " var bar = 2;" + " with (x) { return eval('bar'); }" + "}" + "f(global)")); + script->Run(); + CHECK(try_catch.HasCaught()); + try_catch.Reset(); +} + + +THREADED_TEST(CrossEval) { + v8::HandleScope scope; + LocalContext other; + LocalContext current; + + Local<String> token = v8_str("<security token>"); + other->SetSecurityToken(token); + current->SetSecurityToken(token); + + // Setup reference from current to other. + current->Global()->Set(v8_str("other"), other->Global()); + + // Check that new variables are introduced in other context. + Local<Script> script = + Script::Compile(v8_str("other.eval('var foo = 1234')")); + script->Run(); + Local<Value> foo = other->Global()->Get(v8_str("foo")); + CHECK_EQ(1234, foo->Int32Value()); + CHECK(!current->Global()->Has(v8_str("foo"))); + + // Check that writing to non-existing properties introduces them in + // the other context. + script = + Script::Compile(v8_str("other.eval('na = 1234')")); + script->Run(); + CHECK_EQ(1234, other->Global()->Get(v8_str("na"))->Int32Value()); + CHECK(!current->Global()->Has(v8_str("na"))); + + // Check that global variables in current context are not visible in other + // context. + v8::TryCatch try_catch; + script = + Script::Compile(v8_str("var bar = 42; other.eval('bar');")); + Local<Value> result = script->Run(); + CHECK(try_catch.HasCaught()); + try_catch.Reset(); + + // Check that local variables in current context are not visible in other + // context. + script = + Script::Compile(v8_str("(function() { " + " var baz = 87;" + " return other.eval('baz');" + "})();")); + result = script->Run(); + CHECK(try_catch.HasCaught()); + try_catch.Reset(); + + // Check that global variables in the other environment are visible + // when evaluting code. + other->Global()->Set(v8_str("bis"), v8_num(1234)); + script = Script::Compile(v8_str("other.eval('bis')")); + CHECK_EQ(1234, script->Run()->Int32Value()); + CHECK(!try_catch.HasCaught()); + + // Check that the 'this' pointer points to the global object evaluating + // code. + other->Global()->Set(v8_str("t"), other->Global()); + script = Script::Compile(v8_str("other.eval('this == t')")); + result = script->Run(); + CHECK(result->IsTrue()); + CHECK(!try_catch.HasCaught()); + + // Check that variables introduced in with-statement are not visible in + // other context. + script = + Script::Compile(v8_str("with({x:2}){other.eval('x')}")); + result = script->Run(); + CHECK(try_catch.HasCaught()); + try_catch.Reset(); + + // Check that you cannot use 'eval.call' with another object than the + // current global object. + script = + Script::Compile(v8_str("other.y = 1; eval.call(other, 'y')")); + result = script->Run(); + CHECK(try_catch.HasCaught()); +} + + +THREADED_TEST(CrossLazyLoad) { + v8::HandleScope scope; + LocalContext other; + LocalContext current; + + Local<String> token = v8_str("<security token>"); + other->SetSecurityToken(token); + current->SetSecurityToken(token); + + // Setup reference from current to other. + current->Global()->Set(v8_str("other"), other->Global()); + + // Trigger lazy loading in other context. + Local<Script> script = + Script::Compile(v8_str("other.eval('new Date(42)')")); + Local<Value> value = script->Run(); + CHECK_EQ(42.0, value->NumberValue()); +} + + +static v8::Handle<Value> call_as_function(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + return args[0]; +} + + +// Test that a call handler can be set for objects which will allow +// non-function objects created through the API to be called as +// functions. +THREADED_TEST(CallAsFunction) { + v8::HandleScope scope; + LocalContext context; + + Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(); + Local<ObjectTemplate> instance_template = t->InstanceTemplate(); + instance_template->SetCallAsFunctionHandler(call_as_function); + Local<v8::Object> instance = t->GetFunction()->NewInstance(); + context->Global()->Set(v8_str("obj"), instance); + v8::TryCatch try_catch; + Local<Value> value; + CHECK(!try_catch.HasCaught()); + + value = Script::Compile(v8_str("obj(42)"))->Run(); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(42, value->Int32Value()); + + value = Script::Compile(v8_str("(function(o){return o(49)})(obj)"))->Run(); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(49, value->Int32Value()); + + // test special case of call as function + value = Script::Compile(v8_str("[obj]['0'](45)"))->Run(); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(45, value->Int32Value()); + + value = Script::Compile(v8_str("obj.call = Function.prototype.call;" + "obj.call(null, 87)"))->Run(); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(87, value->Int32Value()); + + // Regression tests for bug #1116356: Calling call through call/apply + // must work for non-function receivers. + const char* apply_99 = "Function.prototype.call.apply(obj, [this, 99])"; + value = Script::Compile(v8_str(apply_99))->Run(); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(99, value->Int32Value()); + + const char* call_17 = "Function.prototype.call.call(obj, this, 17)"; + value = Script::Compile(v8_str(call_17))->Run(); + CHECK(!try_catch.HasCaught()); + CHECK_EQ(17, value->Int32Value()); + + // Try something that will cause an exception: Call the object as a + // constructor. This should be the last test. + value = Script::Compile(v8_str("new obj(42)"))->Run(); + CHECK(try_catch.HasCaught()); +} + + +static int CountHandles() { + return v8::HandleScope::NumberOfHandles(); +} + + +static int Recurse(int depth, int iterations) { + v8::HandleScope scope; + if (depth == 0) return CountHandles(); + for (int i = 0; i < iterations; i++) { + Local<v8::Number> n = v8::Integer::New(42); + } + return Recurse(depth - 1, iterations); +} + + +THREADED_TEST(HandleIteration) { + static const int kIterations = 500; + static const int kNesting = 200; + CHECK_EQ(0, CountHandles()); + { + v8::HandleScope scope1; + CHECK_EQ(0, CountHandles()); + for (int i = 0; i < kIterations; i++) { + Local<v8::Number> n = v8::Integer::New(42); + CHECK_EQ(i + 1, CountHandles()); + } + + CHECK_EQ(kIterations, CountHandles()); + { + v8::HandleScope scope2; + for (int j = 0; j < kIterations; j++) { + Local<v8::Number> n = v8::Integer::New(42); + CHECK_EQ(j + 1 + kIterations, CountHandles()); + } + } + CHECK_EQ(kIterations, CountHandles()); + } + CHECK_EQ(0, CountHandles()); + CHECK_EQ(kNesting * kIterations, Recurse(kNesting, kIterations)); +} + + +static v8::Handle<Value> InterceptorHasOwnPropertyGetter( + Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + return v8::Handle<Value>(); +} + + +THREADED_TEST(InterceptorHasOwnProperty) { + v8::HandleScope scope; + LocalContext context; + Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(); + Local<v8::ObjectTemplate> instance_templ = fun_templ->InstanceTemplate(); + instance_templ->SetNamedPropertyHandler(InterceptorHasOwnPropertyGetter); + Local<Function> function = fun_templ->GetFunction(); + context->Global()->Set(v8_str("constructor"), function); + v8::Handle<Value> value = CompileRun( + "var o = new constructor();" + "o.hasOwnProperty('ostehaps');"); + CHECK_EQ(false, value->BooleanValue()); + value = CompileRun( + "o.ostehaps = 42;" + "o.hasOwnProperty('ostehaps');"); + CHECK_EQ(true, value->BooleanValue()); + value = CompileRun( + "var p = new constructor();" + "p.hasOwnProperty('ostehaps');"); + CHECK_EQ(false, value->BooleanValue()); +} + + +static v8::Handle<Value> InterceptorLoadICGetter(Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(v8_str("x")->Equals(name)); + return v8::Integer::New(42); +} + + +// This test should hit the load IC for the interceptor case. +THREADED_TEST(InterceptorLoadIC) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetNamedPropertyHandler(InterceptorLoadICGetter); + LocalContext context; + context->Global()->Set(v8_str("o"), templ->NewInstance()); + v8::Handle<Value> value = CompileRun( + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " result = o.x;" + "}"); + CHECK_EQ(42, value->Int32Value()); +} + + +static v8::Handle<Value> InterceptorStoreICSetter( + Local<String> key, Local<Value> value, const AccessorInfo&) { + CHECK(v8_str("x")->Equals(key)); + CHECK_EQ(42, value->Int32Value()); + return value; +} + + +// This test should hit the store IC for the interceptor case. +THREADED_TEST(InterceptorStoreIC) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetNamedPropertyHandler(InterceptorLoadICGetter, + InterceptorStoreICSetter); + LocalContext context; + context->Global()->Set(v8_str("o"), templ->NewInstance()); + v8::Handle<Value> value = CompileRun( + "for (var i = 0; i < 1000; i++) {" + " o.x = 42;" + "}"); +} + + + +v8::Handle<Value> call_ic_function; +v8::Handle<Value> call_ic_function2; +v8::Handle<Value> call_ic_function3; + +static v8::Handle<Value> InterceptorCallICGetter(Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + CHECK(v8_str("x")->Equals(name)); + return call_ic_function; +} + + +// This test should hit the call IC for the interceptor case. +THREADED_TEST(InterceptorCallIC) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetNamedPropertyHandler(InterceptorCallICGetter); + LocalContext context; + context->Global()->Set(v8_str("o"), templ->NewInstance()); + call_ic_function = + v8_compile("function f(x) { return x + 1; }; f")->Run(); + v8::Handle<Value> value = CompileRun( + "var result = 0;" + "for (var i = 0; i < 1000; i++) {" + " result = o.x(41);" + "}"); + CHECK_EQ(42, value->Int32Value()); +} + +static int interceptor_call_count = 0; + +static v8::Handle<Value> InterceptorICRefErrorGetter(Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + if (v8_str("x")->Equals(name) && interceptor_call_count++ < 20) { + return call_ic_function2; + } + return v8::Handle<Value>(); +} + + +// This test should hit load and call ICs for the interceptor case. +// Once in a while, the interceptor will reply that a property was not +// found in which case we should get a reference error. +THREADED_TEST(InterceptorICReferenceErrors) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetNamedPropertyHandler(InterceptorICRefErrorGetter); + LocalContext context(0, templ, v8::Handle<Value>()); + call_ic_function2 = v8_compile("function h(x) { return x; }; h")->Run(); + v8::Handle<Value> value = CompileRun( + "function f() {" + " for (var i = 0; i < 1000; i++) {" + " try { x; } catch(e) { return true; }" + " }" + " return false;" + "};" + "f();"); + CHECK_EQ(true, value->BooleanValue()); + interceptor_call_count = 0; + value = CompileRun( + "function g() {" + " for (var i = 0; i < 1000; i++) {" + " try { x(42); } catch(e) { return true; }" + " }" + " return false;" + "};" + "g();"); + CHECK_EQ(true, value->BooleanValue()); +} + + +static int interceptor_ic_exception_get_count = 0; + +static v8::Handle<Value> InterceptorICExceptionGetter( + Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + if (v8_str("x")->Equals(name) && ++interceptor_ic_exception_get_count < 20) { + return call_ic_function3; + } + if (interceptor_ic_exception_get_count == 20) { + return v8::ThrowException(v8_num(42)); + } + // Do not handle get for properties other than x. + return v8::Handle<Value>(); +} + +// Test interceptor load/call IC where the interceptor throws an +// exception once in a while. +THREADED_TEST(InterceptorICGetterExceptions) { + interceptor_ic_exception_get_count = 0; + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetNamedPropertyHandler(InterceptorICExceptionGetter); + LocalContext context(0, templ, v8::Handle<Value>()); + call_ic_function3 = v8_compile("function h(x) { return x; }; h")->Run(); + v8::Handle<Value> value = CompileRun( + "function f() {" + " for (var i = 0; i < 100; i++) {" + " try { x; } catch(e) { return true; }" + " }" + " return false;" + "};" + "f();"); + CHECK_EQ(true, value->BooleanValue()); + interceptor_ic_exception_get_count = 0; + value = CompileRun( + "function f() {" + " for (var i = 0; i < 100; i++) {" + " try { x(42); } catch(e) { return true; }" + " }" + " return false;" + "};" + "f();"); + CHECK_EQ(true, value->BooleanValue()); +} + + +static int interceptor_ic_exception_set_count = 0; + +static v8::Handle<Value> InterceptorICExceptionSetter( + Local<String> key, Local<Value> value, const AccessorInfo&) { + ApiTestFuzzer::Fuzz(); + if (++interceptor_ic_exception_set_count > 20) { + return v8::ThrowException(v8_num(42)); + } + // Do not actually handle setting. + return v8::Handle<Value>(); +} + +// Test interceptor store IC where the interceptor throws an exception +// once in a while. +THREADED_TEST(InterceptorICSetterExceptions) { + interceptor_ic_exception_set_count = 0; + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetNamedPropertyHandler(0, InterceptorICExceptionSetter); + LocalContext context(0, templ, v8::Handle<Value>()); + v8::Handle<Value> value = CompileRun( + "function f() {" + " for (var i = 0; i < 100; i++) {" + " try { x = 42; } catch(e) { return true; }" + " }" + " return false;" + "};" + "f();"); + CHECK_EQ(true, value->BooleanValue()); +} + + +// Test that we ignore null interceptors. +THREADED_TEST(NullNamedInterceptor) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetNamedPropertyHandler(0); + LocalContext context; + templ->Set("x", v8_num(42)); + v8::Handle<v8::Object> obj = templ->NewInstance(); + context->Global()->Set(v8_str("obj"), obj); + v8::Handle<Value> value = CompileRun("obj.x"); + CHECK(value->IsInt32()); + CHECK_EQ(42, value->Int32Value()); +} + + +// Test that we ignore null interceptors. +THREADED_TEST(NullIndexedInterceptor) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetIndexedPropertyHandler(0); + LocalContext context; + templ->Set("42", v8_num(42)); + v8::Handle<v8::Object> obj = templ->NewInstance(); + context->Global()->Set(v8_str("obj"), obj); + v8::Handle<Value> value = CompileRun("obj[42]"); + CHECK(value->IsInt32()); + CHECK_EQ(42, value->Int32Value()); +} + + +static v8::Handle<Value> ParentGetter(Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + return v8_num(1); +} + + +static v8::Handle<Value> ChildGetter(Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + return v8_num(42); +} + + +THREADED_TEST(Overriding) { + v8::HandleScope scope; + LocalContext context; + + // Parent template. + Local<v8::FunctionTemplate> parent_templ = v8::FunctionTemplate::New(); + Local<ObjectTemplate> parent_instance_templ = + parent_templ->InstanceTemplate(); + parent_instance_templ->SetAccessor(v8_str("f"), ParentGetter); + + // Template that inherits from the parent template. + Local<v8::FunctionTemplate> child_templ = v8::FunctionTemplate::New(); + Local<ObjectTemplate> child_instance_templ = + child_templ->InstanceTemplate(); + child_templ->Inherit(parent_templ); + // Override 'f'. The child version of 'f' should get called for child + // instances. + child_instance_templ->SetAccessor(v8_str("f"), ChildGetter); + // Add 'g' twice. The 'g' added last should get called for instances. + child_instance_templ->SetAccessor(v8_str("g"), ParentGetter); + child_instance_templ->SetAccessor(v8_str("g"), ChildGetter); + + // Add 'h' as an accessor to the proto template with ReadOnly attributes + // so 'h' can be shadowed on the instance object. + Local<ObjectTemplate> child_proto_templ = child_templ->PrototypeTemplate(); + child_proto_templ->SetAccessor(v8_str("h"), ParentGetter, 0, + v8::Handle<Value>(), v8::DEFAULT, v8::ReadOnly); + + // Add 'i' as an accessor to the instance template with ReadOnly attributes + // but the attribute does not have effect because it is duplicated with + // NULL setter. + child_instance_templ->SetAccessor(v8_str("i"), ChildGetter, 0, + v8::Handle<Value>(), v8::DEFAULT, v8::ReadOnly); + + + + // Instantiate the child template. + Local<v8::Object> instance = child_templ->GetFunction()->NewInstance(); + + // Check that the child function overrides the parent one. + context->Global()->Set(v8_str("o"), instance); + Local<Value> value = v8_compile("o.f")->Run(); + // Check that the 'g' that was added last is hit. + CHECK_EQ(42, value->Int32Value()); + value = v8_compile("o.g")->Run(); + CHECK_EQ(42, value->Int32Value()); + + // Check 'h' can be shadowed. + value = v8_compile("o.h = 3; o.h")->Run(); + CHECK_EQ(3, value->Int32Value()); + + // Check 'i' is cannot be shadowed or changed. + value = v8_compile("o.i = 3; o.i")->Run(); + CHECK_EQ(42, value->Int32Value()); +} + + +static v8::Handle<Value> IsConstructHandler(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + if (args.IsConstructCall()) { + return v8::Boolean::New(true); + } + return v8::Boolean::New(false); +} + + +THREADED_TEST(IsConstructCall) { + v8::HandleScope scope; + + // Function template with call handler. + Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(); + templ->SetCallHandler(IsConstructHandler); + + LocalContext context; + + context->Global()->Set(v8_str("f"), templ->GetFunction()); + Local<Value> value = v8_compile("f()")->Run(); + CHECK(!value->BooleanValue()); + value = v8_compile("new f()")->Run(); + CHECK(value->BooleanValue()); +} + + +THREADED_TEST(ObjectProtoToString) { + v8::HandleScope scope; + Local<v8::FunctionTemplate> templ = v8::FunctionTemplate::New(); + templ->SetClassName(v8_str("MyClass")); + + LocalContext context; + + Local<String> customized_tostring = v8_str("customized toString"); + + // Replace Object.prototype.toString + v8_compile("Object.prototype.toString = function() {" + " return 'customized toString';" + "}")->Run(); + + // Normal ToString call should call replaced Object.prototype.toString + Local<v8::Object> instance = templ->GetFunction()->NewInstance(); + Local<String> value = instance->ToString(); + CHECK(value->IsString() && value->Equals(customized_tostring)); + + // ObjectProtoToString should not call replace toString function. + value = instance->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object MyClass]"))); + + // Check global + value = context->Global()->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object global]"))); + + // Check ordinary object + Local<Value> object = v8_compile("new Object()")->Run(); + value = Local<v8::Object>::Cast(object)->ObjectProtoToString(); + CHECK(value->IsString() && value->Equals(v8_str("[object Object]"))); +} + + +bool ApiTestFuzzer::fuzzing_ = false; +v8::internal::Semaphore* ApiTestFuzzer::all_tests_done_= + v8::internal::OS::CreateSemaphore(0); +int ApiTestFuzzer::active_tests_; +int ApiTestFuzzer::tests_being_run_; +int ApiTestFuzzer::current_; + + +// We are in a callback and want to switch to another thread (if we +// are currently running the thread fuzzing test). +void ApiTestFuzzer::Fuzz() { + if (!fuzzing_) return; + ApiTestFuzzer* test = RegisterThreadedTest::nth(current_)->fuzzer_; + test->ContextSwitch(); +} + + +// Let the next thread go. Since it is also waiting on the V8 lock it may +// not start immediately. +bool ApiTestFuzzer::NextThread() { + int test_position = GetNextTestNumber(); + int test_number = RegisterThreadedTest::nth(current_)->fuzzer_->test_number_; + if (test_position == current_) { + printf("Stay with %d\n", test_number); + return false; + } + printf("Switch from %d to %d\n", + current_ < 0 ? 0 : test_number, test_position < 0 ? 0 : test_number); + current_ = test_position; + RegisterThreadedTest::nth(current_)->fuzzer_->gate_->Signal(); + return true; +} + + +void ApiTestFuzzer::Run() { + // When it is our turn... + gate_->Wait(); + { + // ... get the V8 lock and start running the test. + v8::Locker locker; + CallTest(); + } + // This test finished. + active_ = false; + active_tests_--; + // If it was the last then signal that fact. + if (active_tests_ == 0) { + all_tests_done_->Signal(); + } else { + // Otherwise select a new test and start that. + NextThread(); + } +} + + +static unsigned linear_congruential_generator; + + +void ApiTestFuzzer::Setup(PartOfTest part) { + linear_congruential_generator = i::FLAG_testing_prng_seed; + fuzzing_ = true; + int start = (part == FIRST_PART) ? 0 : (RegisterThreadedTest::count() >> 1); + int end = (part == FIRST_PART) + ? (RegisterThreadedTest::count() >> 1) + : RegisterThreadedTest::count(); + active_tests_ = tests_being_run_ = end - start; + for (int i = 0; i < tests_being_run_; i++) { + RegisterThreadedTest::nth(i)->fuzzer_ = new ApiTestFuzzer(i + start); + } + for (int i = 0; i < active_tests_; i++) { + RegisterThreadedTest::nth(i)->fuzzer_->Start(); + } +} + + +static void CallTestNumber(int test_number) { + (RegisterThreadedTest::nth(test_number)->callback())(); +} + + +void ApiTestFuzzer::RunAllTests() { + // Set off the first test. + current_ = -1; + NextThread(); + // Wait till they are all done. + all_tests_done_->Wait(); +} + + +int ApiTestFuzzer::GetNextTestNumber() { + int next_test; + do { + next_test = (linear_congruential_generator >> 16) % tests_being_run_; + linear_congruential_generator *= 1664525u; + linear_congruential_generator += 1013904223u; + } while (!RegisterThreadedTest::nth(next_test)->fuzzer_->active_); + return next_test; +} + + +void ApiTestFuzzer::ContextSwitch() { + // If the new thread is the same as the current thread there is nothing to do. + if (NextThread()) { + // Now it can start. + v8::Unlocker unlocker; + // Wait till someone starts us again. + gate_->Wait(); + // And we're off. + } +} + + +void ApiTestFuzzer::TearDown() { + fuzzing_ = false; + for (int i = 0; i < RegisterThreadedTest::count(); i++) { + ApiTestFuzzer *fuzzer = RegisterThreadedTest::nth(i)->fuzzer_; + if (fuzzer != NULL) fuzzer->Join(); + } +} + + +// Lets not be needlessly self-referential. +TEST(Threading) { + ApiTestFuzzer::Setup(ApiTestFuzzer::FIRST_PART); + ApiTestFuzzer::RunAllTests(); + ApiTestFuzzer::TearDown(); +} + +TEST(Threading2) { + ApiTestFuzzer::Setup(ApiTestFuzzer::SECOND_PART); + ApiTestFuzzer::RunAllTests(); + ApiTestFuzzer::TearDown(); +} + + +void ApiTestFuzzer::CallTest() { + printf("Start test %d\n", test_number_); + CallTestNumber(test_number_); + printf("End test %d\n", test_number_); +} + + +static v8::Handle<Value> ThrowInJS(const v8::Arguments& args) { + CHECK(v8::Locker::IsLocked()); + ApiTestFuzzer::Fuzz(); + v8::Unlocker unlocker; + const char* code = "throw 7;"; + { + v8::Locker nested_locker; + v8::HandleScope scope; + v8::Handle<Value> exception; + { v8::TryCatch try_catch; + v8::Handle<Value> value = CompileRun(code); + CHECK(value.IsEmpty()); + CHECK(try_catch.HasCaught()); + // Make sure to wrap the exception in a new handle because + // the handle returned from the TryCatch is destroyed + // when the TryCatch is destroyed. + exception = Local<Value>::New(try_catch.Exception()); + } + return v8::ThrowException(exception); + } +} + + +static v8::Handle<Value> ThrowInJSNoCatch(const v8::Arguments& args) { + CHECK(v8::Locker::IsLocked()); + ApiTestFuzzer::Fuzz(); + v8::Unlocker unlocker; + const char* code = "throw 7;"; + { + v8::Locker nested_locker; + v8::HandleScope scope; + v8::Handle<Value> value = CompileRun(code); + CHECK(value.IsEmpty()); + return v8_str("foo"); + } +} + + +// These are locking tests that don't need to be run again +// as part of the locking aggregation tests. +TEST(NestedLockers) { + v8::Locker locker; + CHECK(v8::Locker::IsLocked()); + v8::HandleScope scope; + LocalContext env; + Local<v8::FunctionTemplate> fun_templ = v8::FunctionTemplate::New(ThrowInJS); + Local<Function> fun = fun_templ->GetFunction(); + env->Global()->Set(v8_str("throw_in_js"), fun); + Local<Script> script = v8_compile("(function () {" + " try {" + " throw_in_js();" + " return 42;" + " } catch (e) {" + " return e * 13;" + " }" + "})();"); + CHECK_EQ(91, script->Run()->Int32Value()); +} + + +// These are locking tests that don't need to be run again +// as part of the locking aggregation tests. +TEST(NestedLockersNoTryCatch) { + v8::Locker locker; + v8::HandleScope scope; + LocalContext env; + Local<v8::FunctionTemplate> fun_templ = + v8::FunctionTemplate::New(ThrowInJSNoCatch); + Local<Function> fun = fun_templ->GetFunction(); + env->Global()->Set(v8_str("throw_in_js"), fun); + Local<Script> script = v8_compile("(function () {" + " try {" + " throw_in_js();" + " return 42;" + " } catch (e) {" + " return e * 13;" + " }" + "})();"); + CHECK_EQ(91, script->Run()->Int32Value()); +} + + +THREADED_TEST(RecursiveLocking) { + v8::Locker locker; + { + v8::Locker locker2; + CHECK(v8::Locker::IsLocked()); + } +} + + +static v8::Handle<Value> UnlockForAMoment(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + v8::Unlocker unlocker; + return v8::Undefined(); +} + + +THREADED_TEST(LockUnlockLock) { + { + v8::Locker locker; + v8::HandleScope scope; + LocalContext env; + Local<v8::FunctionTemplate> fun_templ = + v8::FunctionTemplate::New(UnlockForAMoment); + Local<Function> fun = fun_templ->GetFunction(); + env->Global()->Set(v8_str("unlock_for_a_moment"), fun); + Local<Script> script = v8_compile("(function () {" + " unlock_for_a_moment();" + " return 42;" + "})();"); + CHECK_EQ(42, script->Run()->Int32Value()); + } + { + v8::Locker locker; + v8::HandleScope scope; + LocalContext env; + Local<v8::FunctionTemplate> fun_templ = + v8::FunctionTemplate::New(UnlockForAMoment); + Local<Function> fun = fun_templ->GetFunction(); + env->Global()->Set(v8_str("unlock_for_a_moment"), fun); + Local<Script> script = v8_compile("(function () {" + " unlock_for_a_moment();" + " return 42;" + "})();"); + CHECK_EQ(42, script->Run()->Int32Value()); + } +} + + +static int GetSurvivingGlobalObjectsCount() { + int count = 0; + // We need to collect all garbage twice to be sure that everything + // has been collected. This is because inline caches are cleared in + // the first garbage collection but some of the maps have already + // been marked at that point. Therefore some of the maps are not + // collected until the second garbage collection. + v8::internal::Heap::CollectAllGarbage(); + v8::internal::Heap::CollectAllGarbage(); + v8::internal::HeapIterator it; + while (it.has_next()) { + v8::internal::HeapObject* object = it.next(); + if (object->IsJSGlobalObject()) { + count++; + } + } +#ifdef DEBUG + if (count > 0) v8::internal::Heap::TracePathToGlobal(); +#endif + return count; +} + + +TEST(DontLeakGlobalObjects) { + // Regression test for issues 1139850 and 1174891. + + v8::V8::Initialize(); + + int count = GetSurvivingGlobalObjectsCount(); + + for (int i = 0; i < 5; i++) { + { v8::HandleScope scope; + LocalContext context; + } + CHECK_EQ(count, GetSurvivingGlobalObjectsCount()); + + { v8::HandleScope scope; + LocalContext context; + v8_compile("Date")->Run(); + } + CHECK_EQ(count, GetSurvivingGlobalObjectsCount()); + + { v8::HandleScope scope; + LocalContext context; + v8_compile("/aaa/")->Run(); + } + CHECK_EQ(count, GetSurvivingGlobalObjectsCount()); + + { v8::HandleScope scope; + const char* extension_list[] = { "v8/gc" }; + v8::ExtensionConfiguration extensions(1, extension_list); + LocalContext context(&extensions); + v8_compile("gc();")->Run(); + } + CHECK_EQ(count, GetSurvivingGlobalObjectsCount()); + } +} + + +THREADED_TEST(CheckForCrossContextObjectLiterals) { + v8::V8::Initialize(); + + const int nof = 2; + const char* sources[nof] = { + "try { [ 2, 3, 4 ].forEach(5); } catch(e) { e.toString(); }", + "Object()" + }; + + for (int i = 0; i < nof; i++) { + const char* source = sources[i]; + { v8::HandleScope scope; + LocalContext context; + CompileRun(source); + } + { v8::HandleScope scope; + LocalContext context; + CompileRun(source); + } + } +} + + +static v8::Handle<Value> NestedScope(v8::Persistent<Context> env) { + v8::HandleScope inner; + env->Enter(); + v8::Handle<Value> three = v8_num(3); + v8::Handle<Value> value = inner.Close(three); + env->Exit(); + return value; +} + + +THREADED_TEST(NestedHandleScopeAndContexts) { + v8::HandleScope outer; + v8::Persistent<Context> env = Context::New(); + env->Enter(); + v8::Handle<Value> value = NestedScope(env); + v8::Handle<String> str = value->ToString(); + env->Exit(); + env.Dispose(); +} + + +THREADED_TEST(ExternalAllocatedMemory) { + v8::HandleScope outer; + const int kSize = 1024*1024; + CHECK_EQ(v8::V8::AdjustAmountOfExternalAllocatedMemory(kSize), kSize); + CHECK_EQ(v8::V8::AdjustAmountOfExternalAllocatedMemory(-kSize), 0); +} + + +THREADED_TEST(DisposeEnteredContext) { + v8::HandleScope scope; + LocalContext outer; + { v8::Persistent<v8::Context> inner = v8::Context::New(); + inner->Enter(); + inner.Dispose(); + inner.Clear(); + inner->Exit(); + } +} + + +// Regression test for issue 54, object templates with internal fields +// but no accessors or interceptors did not get their internal field +// count set on instances. +THREADED_TEST(Regress54) { + v8::HandleScope outer; + LocalContext context; + static v8::Persistent<v8::ObjectTemplate> templ; + if (templ.IsEmpty()) { + v8::HandleScope inner; + v8::Handle<v8::ObjectTemplate> local = v8::ObjectTemplate::New(); + local->SetInternalFieldCount(1); + templ = v8::Persistent<v8::ObjectTemplate>::New(inner.Close(local)); + } + v8::Handle<v8::Object> result = templ->NewInstance(); + CHECK_EQ(1, result->InternalFieldCount()); +} + + +// If part of the threaded tests, this test makes ThreadingTest fail +// on mac. +TEST(CatchStackOverflow) { + v8::HandleScope scope; + LocalContext context; + v8::TryCatch try_catch; + v8::Handle<v8::Script> script = v8::Script::Compile(v8::String::New( + "function f() {" + " return f();" + "}" + "" + "f();")); + v8::Handle<v8::Value> result = script->Run(); + CHECK(result.IsEmpty()); +} + + +static void CheckTryCatchSourceInfo(v8::Handle<v8::Script> script, + const char* resource_name, + int line_offset) { + v8::HandleScope scope; + v8::TryCatch try_catch; + v8::Handle<v8::Value> result = script->Run(); + CHECK(result.IsEmpty()); + CHECK(try_catch.HasCaught()); + v8::Handle<v8::Message> message = try_catch.Message(); + CHECK(!message.IsEmpty()); + CHECK_EQ(10 + line_offset, message->GetLineNumber()); + CHECK_EQ(91, message->GetStartPosition()); + CHECK_EQ(92, message->GetEndPosition()); + CHECK_EQ(2, message->GetStartColumn()); + CHECK_EQ(3, message->GetEndColumn()); + v8::String::AsciiValue line(message->GetSourceLine()); + CHECK_EQ(" throw 'nirk';", *line); + v8::String::AsciiValue name(message->GetScriptResourceName()); + CHECK_EQ(resource_name, *name); +} + + +THREADED_TEST(TryCatchSourceInfo) { + v8::HandleScope scope; + LocalContext context; + v8::Handle<v8::String> source = v8::String::New( + "function Foo() {\n" + " return Bar();\n" + "}\n" + "\n" + "function Bar() {\n" + " return Baz();\n" + "}\n" + "\n" + "function Baz() {\n" + " throw 'nirk';\n" + "}\n" + "\n" + "Foo();\n"); + + const char* resource_name; + v8::Handle<v8::Script> script; + resource_name = "test.js"; + script = v8::Script::Compile(source, v8::String::New(resource_name)); + CheckTryCatchSourceInfo(script, resource_name, 0); + + resource_name = "test1.js"; + v8::ScriptOrigin origin1(v8::String::New(resource_name)); + script = v8::Script::Compile(source, &origin1); + CheckTryCatchSourceInfo(script, resource_name, 0); + + resource_name = "test2.js"; + v8::ScriptOrigin origin2(v8::String::New(resource_name), v8::Integer::New(7)); + script = v8::Script::Compile(source, &origin2); + CheckTryCatchSourceInfo(script, resource_name, 7); +} + + +THREADED_TEST(CompilationCache) { + v8::HandleScope scope; + LocalContext context; + v8::Handle<v8::String> source0 = v8::String::New("1234"); + v8::Handle<v8::String> source1 = v8::String::New("1234"); + v8::Handle<v8::Script> script0 = + v8::Script::Compile(source0, v8::String::New("test.js")); + v8::Handle<v8::Script> script1 = + v8::Script::Compile(source1, v8::String::New("test.js")); + v8::Handle<v8::Script> script2 = + v8::Script::Compile(source0); // different origin + CHECK_EQ(1234, script0->Run()->Int32Value()); + CHECK_EQ(1234, script1->Run()->Int32Value()); + CHECK_EQ(1234, script2->Run()->Int32Value()); +} + + +static v8::Handle<Value> FunctionNameCallback(const v8::Arguments& args) { + ApiTestFuzzer::Fuzz(); + return v8_num(42); +} + + +THREADED_TEST(CallbackFunctionName) { + v8::HandleScope scope; + LocalContext context; + Local<ObjectTemplate> t = ObjectTemplate::New(); + t->Set(v8_str("asdf"), v8::FunctionTemplate::New(FunctionNameCallback)); + context->Global()->Set(v8_str("obj"), t->NewInstance()); + v8::Handle<v8::Value> value = CompileRun("obj.asdf.name"); + CHECK(value->IsString()); + v8::String::AsciiValue name(value); + CHECK_EQ("asdf", *name); +} + + +THREADED_TEST(DateAccess) { + v8::HandleScope scope; + LocalContext context; + v8::Handle<v8::Value> date = v8::Date::New(1224744689038.0); + CHECK(date->IsDate()); + CHECK_EQ(1224744689038.0, v8::Handle<v8::Date>::Cast(date)->NumberValue()); +} + + +void CheckProperties(v8::Handle<v8::Value> val, int elmc, const char* elmv[]) { + v8::Handle<v8::Object> obj = v8::Handle<v8::Object>::Cast(val); + v8::Handle<v8::Array> props = obj->GetPropertyNames(); + CHECK_EQ(elmc, props->Length()); + for (int i = 0; i < elmc; i++) { + v8::String::Utf8Value elm(props->Get(v8::Integer::New(i))); + CHECK_EQ(elmv[i], *elm); + } +} + + +THREADED_TEST(PropertyEnumeration) { + v8::HandleScope scope; + LocalContext context; + v8::Handle<v8::Value> obj = v8::Script::Compile(v8::String::New( + "var result = [];" + "result[0] = {};" + "result[1] = {a: 1, b: 2};" + "result[2] = [1, 2, 3];" + "var proto = {x: 1, y: 2, z: 3};" + "var x = { __proto__: proto, w: 0, z: 1 };" + "result[3] = x;" + "result;"))->Run(); + v8::Handle<v8::Array> elms = v8::Handle<v8::Array>::Cast(obj); + CHECK_EQ(4, elms->Length()); + int elmc0 = 0; + const char** elmv0 = NULL; + CheckProperties(elms->Get(v8::Integer::New(0)), elmc0, elmv0); + int elmc1 = 2; + const char* elmv1[] = {"a", "b"}; + CheckProperties(elms->Get(v8::Integer::New(1)), elmc1, elmv1); + int elmc2 = 3; + const char* elmv2[] = {"0", "1", "2"}; + CheckProperties(elms->Get(v8::Integer::New(2)), elmc2, elmv2); + int elmc3 = 4; + const char* elmv3[] = {"w", "z", "x", "y"}; + CheckProperties(elms->Get(v8::Integer::New(3)), elmc3, elmv3); +} + + +static v8::Handle<Value> AccessorProhibitsOverwritingGetter( + Local<String> name, + const AccessorInfo& info) { + ApiTestFuzzer::Fuzz(); + return v8::True(); +} + + +THREADED_TEST(AccessorProhibitsOverwriting) { + v8::HandleScope scope; + LocalContext context; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetAccessor(v8_str("x"), + AccessorProhibitsOverwritingGetter, + 0, + v8::Handle<Value>(), + v8::PROHIBITS_OVERWRITING, + v8::ReadOnly); + Local<v8::Object> instance = templ->NewInstance(); + context->Global()->Set(v8_str("obj"), instance); + Local<Value> value = CompileRun( + "obj.__defineGetter__('x', function() { return false; });" + "obj.x"); + CHECK(value->BooleanValue()); + value = CompileRun( + "var setter_called = false;" + "obj.__defineSetter__('x', function() { setter_called = true; });" + "obj.x = 42;" + "setter_called"); + CHECK(!value->BooleanValue()); + value = CompileRun( + "obj2 = {};" + "obj2.__proto__ = obj;" + "obj2.__defineGetter__('x', function() { return false; });" + "obj2.x"); + CHECK(value->BooleanValue()); + value = CompileRun( + "var setter_called = false;" + "obj2 = {};" + "obj2.__proto__ = obj;" + "obj2.__defineSetter__('x', function() { setter_called = true; });" + "obj2.x = 42;" + "setter_called"); + CHECK(!value->BooleanValue()); +} + + +static bool NamedSetAccessBlocker(Local<v8::Object> obj, + Local<Value> name, + v8::AccessType type, + Local<Value> data) { + return type != v8::ACCESS_SET; +} + + +static bool IndexedSetAccessBlocker(Local<v8::Object> obj, + uint32_t key, + v8::AccessType type, + Local<Value> data) { + return type != v8::ACCESS_SET; +} + + +THREADED_TEST(DisableAccessChecksWhileConfiguring) { + v8::HandleScope scope; + LocalContext context; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetAccessCheckCallbacks(NamedSetAccessBlocker, + IndexedSetAccessBlocker); + templ->Set(v8_str("x"), v8::True()); + Local<v8::Object> instance = templ->NewInstance(); + context->Global()->Set(v8_str("obj"), instance); + Local<Value> value = CompileRun("obj.x"); + CHECK(value->BooleanValue()); +} + +static bool NamedGetAccessBlocker(Local<v8::Object> obj, + Local<Value> name, + v8::AccessType type, + Local<Value> data) { + return false; +} + + +static bool IndexedGetAccessBlocker(Local<v8::Object> obj, + uint32_t key, + v8::AccessType type, + Local<Value> data) { + return false; +} + + + +THREADED_TEST(AccessChecksReenabledCorrectly) { + v8::HandleScope scope; + LocalContext context; + Local<ObjectTemplate> templ = ObjectTemplate::New(); + templ->SetAccessCheckCallbacks(NamedGetAccessBlocker, + IndexedGetAccessBlocker); + templ->Set(v8_str("a"), v8_str("a")); + // Add more than 8 (see kMaxFastProperties) properties + // so that the constructor will force copying map. + // Cannot sprintf, gcc complains unsafety. + char buf[4]; + for (char i = '0'; i <= '9' ; i++) { + buf[0] = i; + for (char j = '0'; j <= '9'; j++) { + buf[1] = j; + for (char k = '0'; k <= '9'; k++) { + buf[2] = k; + buf[3] = 0; + templ->Set(v8_str(buf), v8::Number::New(k)); + } + } + } + + Local<v8::Object> instance_1 = templ->NewInstance(); + context->Global()->Set(v8_str("obj_1"), instance_1); + + Local<Value> value_1 = CompileRun("obj_1.a"); + CHECK(value_1->IsUndefined()); + + Local<v8::Object> instance_2 = templ->NewInstance(); + context->Global()->Set(v8_str("obj_2"), instance_2); + + Local<Value> value_2 = CompileRun("obj_2.a"); + CHECK(value_2->IsUndefined()); +} + +// This tests that access check information remains on the global +// object template when creating contexts. +THREADED_TEST(AccessControlRepeatedContextCreation) { + v8::HandleScope handle_scope; + v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(); + global_template->SetAccessCheckCallbacks(NamedSetAccessBlocker, + IndexedSetAccessBlocker); + i::Handle<i::ObjectTemplateInfo> internal_template = + v8::Utils::OpenHandle(*global_template); + CHECK(!internal_template->constructor()->IsUndefined()); + i::Handle<i::FunctionTemplateInfo> constructor( + i::FunctionTemplateInfo::cast(internal_template->constructor())); + CHECK(!constructor->access_check_info()->IsUndefined()); + v8::Persistent<Context> context0 = Context::New(NULL, global_template); + CHECK(!constructor->access_check_info()->IsUndefined()); +} + + +// This test verifies that pre-compilation (aka preparsing) can be called +// without initializing the whole VM. Thus we cannot run this test in a +// multi-threaded setup. +TEST(PreCompile) { + // TODO(155): This test would break without the initialization of V8. This is + // a workaround for now to make this test not fail. + v8::V8::Initialize(); + const char *script = "function foo(a) { return a+1; }"; + v8::ScriptData *sd = v8::ScriptData::PreCompile(script, strlen(script)); + CHECK_NE(sd->Length(), 0); + CHECK_NE(sd->Data(), NULL); + delete sd; +} + + +// This tests that we do not allow dictionary load/call inline caches +// to use functions that have not yet been compiled. The potential +// problem of loading a function that has not yet been compiled can +// arise because we share code between contexts via the compilation +// cache. +THREADED_TEST(DictionaryICLoadedFunction) { + v8::HandleScope scope; + // Test LoadIC. + for (int i = 0; i < 2; i++) { + LocalContext context; + context->Global()->Set(v8_str("tmp"), v8::True()); + context->Global()->Delete(v8_str("tmp")); + CompileRun("for (var j = 0; j < 10; j++) new RegExp('');"); + } + // Test CallIC. + for (int i = 0; i < 2; i++) { + LocalContext context; + context->Global()->Set(v8_str("tmp"), v8::True()); + context->Global()->Delete(v8_str("tmp")); + CompileRun("for (var j = 0; j < 10; j++) RegExp('')"); + } +} + + +// Test that cross-context new calls use the context of the callee to +// create the new JavaScript object. +THREADED_TEST(CrossContextNew) { + v8::HandleScope scope; + v8::Persistent<Context> context0 = Context::New(); + v8::Persistent<Context> context1 = Context::New(); + + // Allow cross-domain access. + Local<String> token = v8_str("<security token>"); + context0->SetSecurityToken(token); + context1->SetSecurityToken(token); + + // Set an 'x' property on the Object prototype and define a + // constructor function in context0. + context0->Enter(); + CompileRun("Object.prototype.x = 42; function C() {};"); + context0->Exit(); + + // Call the constructor function from context0 and check that the + // result has the 'x' property. + context1->Enter(); + context1->Global()->Set(v8_str("other"), context0->Global()); + Local<Value> value = CompileRun("var instance = new other.C(); instance.x"); + CHECK(value->IsInt32()); + CHECK_EQ(42, value->Int32Value()); + context1->Exit(); + + // Dispose the contexts to allow them to be garbage collected. + context0.Dispose(); + context1.Dispose(); +} + + +class RegExpInterruptTest { + public: + RegExpInterruptTest() : block_(NULL) {} + ~RegExpInterruptTest() { delete block_; } + void RunTest() { + block_ = i::OS::CreateSemaphore(0); + gc_count_ = 0; + gc_during_regexp_ = 0; + regexp_success_ = false; + gc_success_ = false; + GCThread gc_thread(this); + gc_thread.Start(); + v8::Locker::StartPreemption(1); + + LongRunningRegExp(); + { + v8::Unlocker unlock; + gc_thread.Join(); + } + v8::Locker::StopPreemption(); + CHECK(regexp_success_); + CHECK(gc_success_); + } + private: + // Number of garbage collections required. + static const int kRequiredGCs = 5; + + class GCThread : public i::Thread { + public: + explicit GCThread(RegExpInterruptTest* test) + : test_(test) {} + virtual void Run() { + test_->CollectGarbage(); + } + private: + RegExpInterruptTest* test_; + }; + + void CollectGarbage() { + block_->Wait(); + while (gc_during_regexp_ < kRequiredGCs) { + { + v8::Locker lock; + // TODO(lrn): Perhaps create some garbage before collecting. + i::Heap::CollectAllGarbage(); + gc_count_++; + } + i::OS::Sleep(1); + } + gc_success_ = true; + } + + void LongRunningRegExp() { + block_->Signal(); // Enable garbage collection thread on next preemption. + int rounds = 0; + while (gc_during_regexp_ < kRequiredGCs) { + int gc_before = gc_count_; + { + // Match 15-30 "a"'s against 14 and a "b". + const char* c_source = + "/a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaa/" + ".exec('aaaaaaaaaaaaaaab') === null"; + Local<String> source = String::New(c_source); + Local<Script> script = Script::Compile(source); + Local<Value> result = script->Run(); + if (!result->BooleanValue()) { + gc_during_regexp_ = kRequiredGCs; // Allow gc thread to exit. + return; + } + } + { + // Match 15-30 "a"'s against 15 and a "b". + const char* c_source = + "/a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaa/" + ".exec('aaaaaaaaaaaaaaaab')[0] === 'aaaaaaaaaaaaaaaa'"; + Local<String> source = String::New(c_source); + Local<Script> script = Script::Compile(source); + Local<Value> result = script->Run(); + if (!result->BooleanValue()) { + gc_during_regexp_ = kRequiredGCs; + return; + } + } + int gc_after = gc_count_; + gc_during_regexp_ += gc_after - gc_before; + rounds++; + i::OS::Sleep(1); + } + regexp_success_ = true; + } + + i::Semaphore* block_; + int gc_count_; + int gc_during_regexp_; + bool regexp_success_; + bool gc_success_; +}; + + +// Test that a regular expression execution can be interrupted and +// survive a garbage collection. +TEST(RegExpInterruption) { + v8::Locker lock; + v8::V8::Initialize(); + v8::HandleScope scope; + Local<Context> local_env; + { + LocalContext env; + local_env = env.local(); + } + + // Local context should still be live. + CHECK(!local_env.IsEmpty()); + local_env->Enter(); + + // Should complete without problems. + RegExpInterruptTest().RunTest(); + + local_env->Exit(); +} + + +// Verify that we can clone an object +TEST(ObjectClone) { + v8::HandleScope scope; + LocalContext env; + + const char* sample = + "var rv = {};" \ + "rv.alpha = 'hello';" \ + "rv.beta = 123;" \ + "rv;"; + + // Create an object, verify basics. + Local<Value> val = CompileRun(sample); + CHECK(val->IsObject()); + Local<v8::Object> obj = Local<v8::Object>::Cast(val); + obj->Set(v8_str("gamma"), v8_str("cloneme")); + + CHECK_EQ(v8_str("hello"), obj->Get(v8_str("alpha"))); + CHECK_EQ(v8::Integer::New(123), obj->Get(v8_str("beta"))); + CHECK_EQ(v8_str("cloneme"), obj->Get(v8_str("gamma"))); + + // Clone it. + Local<v8::Object> clone = obj->Clone(); + CHECK_EQ(v8_str("hello"), clone->Get(v8_str("alpha"))); + CHECK_EQ(v8::Integer::New(123), clone->Get(v8_str("beta"))); + CHECK_EQ(v8_str("cloneme"), clone->Get(v8_str("gamma"))); + + // Set a property on the clone, verify each object. + clone->Set(v8_str("beta"), v8::Integer::New(456)); + CHECK_EQ(v8::Integer::New(123), obj->Get(v8_str("beta"))); + CHECK_EQ(v8::Integer::New(456), clone->Get(v8_str("beta"))); +} + + +class RegExpStringModificationTest { + public: + RegExpStringModificationTest() + : block_(i::OS::CreateSemaphore(0)), + morphs_(0), + morphs_during_regexp_(0), + ascii_resource_(i::Vector<const char>("aaaaaaaaaaaaaab", 15)), + uc16_resource_(i::Vector<const uint16_t>(two_byte_content_, 15)) {} + ~RegExpStringModificationTest() { delete block_; } + void RunTest() { + regexp_success_ = false; + morph_success_ = false; + + // Initialize the contents of two_byte_content_ to be a uc16 representation + // of "aaaaaaaaaaaaaab". + for (int i = 0; i < 14; i++) { + two_byte_content_[i] = 'a'; + } + two_byte_content_[14] = 'b'; + + // Create the input string for the regexp - the one we are going to change + // properties of. + input_ = i::Factory::NewExternalStringFromAscii(&ascii_resource_); + + // Inject the input as a global variable. + i::Handle<i::String> input_name = + i::Factory::NewStringFromAscii(i::Vector<const char>("input", 5)); + i::Top::global_context()->global()->SetProperty(*input_name, *input_, NONE); + + + MorphThread morph_thread(this); + morph_thread.Start(); + v8::Locker::StartPreemption(1); + LongRunningRegExp(); + { + v8::Unlocker unlock; + morph_thread.Join(); + } + v8::Locker::StopPreemption(); + CHECK(regexp_success_); + CHECK(morph_success_); + } + private: + + class AsciiVectorResource : public v8::String::ExternalAsciiStringResource { + public: + explicit AsciiVectorResource(i::Vector<const char> vector) + : data_(vector) {} + virtual ~AsciiVectorResource() {} + virtual size_t length() const { return data_.length(); } + virtual const char* data() const { return data_.start(); } + private: + i::Vector<const char> data_; + }; + class UC16VectorResource : public v8::String::ExternalStringResource { + public: + explicit UC16VectorResource(i::Vector<const i::uc16> vector) + : data_(vector) {} + virtual ~UC16VectorResource() {} + virtual size_t length() const { return data_.length(); } + virtual const i::uc16* data() const { return data_.start(); } + private: + i::Vector<const i::uc16> data_; + }; + // Number of string modifications required. + static const int kRequiredModifications = 5; + static const int kMaxModifications = 100; + + class MorphThread : public i::Thread { + public: + explicit MorphThread(RegExpStringModificationTest* test) + : test_(test) {} + virtual void Run() { + test_->MorphString(); + } + private: + RegExpStringModificationTest* test_; + }; + + void MorphString() { + block_->Wait(); + while (morphs_during_regexp_ < kRequiredModifications && + morphs_ < kMaxModifications) { + { + v8::Locker lock; + // Swap string between ascii and two-byte representation. + i::String* string = *input_; + CHECK(i::StringShape(string).IsExternal()); + if (i::StringShape(string).IsAsciiRepresentation()) { + // Morph external string to be TwoByte string. + i::ExternalAsciiString* ext_string = + i::ExternalAsciiString::cast(string); + i::ExternalTwoByteString* morphed = + reinterpret_cast<i::ExternalTwoByteString*>(ext_string); + morphed->map()->set_instance_type(i::SHORT_EXTERNAL_STRING_TYPE); + morphed->set_resource(&uc16_resource_); + } else { + // Morph external string to be ASCII string. + i::ExternalTwoByteString* ext_string = + i::ExternalTwoByteString::cast(string); + i::ExternalAsciiString* morphed = + reinterpret_cast<i::ExternalAsciiString*>(ext_string); + morphed->map()->set_instance_type( + i::SHORT_EXTERNAL_ASCII_STRING_TYPE); + morphed->set_resource(&ascii_resource_); + } + morphs_++; + } + i::OS::Sleep(1); + } + morph_success_ = true; + } + + void LongRunningRegExp() { + block_->Signal(); // Enable morphing thread on next preemption. + while (morphs_during_regexp_ < kRequiredModifications && + morphs_ < kMaxModifications) { + int morphs_before = morphs_; + { + // Match 15-30 "a"'s against 14 and a "b". + const char* c_source = + "/a?a?a?a?a?a?a?a?a?a?a?a?a?a?aaaaaaaaaaaaaaaa/" + ".exec(input) === null"; + Local<String> source = String::New(c_source); + Local<Script> script = Script::Compile(source); + Local<Value> result = script->Run(); + CHECK(result->IsTrue()); + } + int morphs_after = morphs_; + morphs_during_regexp_ += morphs_after - morphs_before; + } + regexp_success_ = true; + } + + i::uc16 two_byte_content_[15]; + i::Semaphore* block_; + int morphs_; + int morphs_during_regexp_; + bool regexp_success_; + bool morph_success_; + i::Handle<i::String> input_; + AsciiVectorResource ascii_resource_; + UC16VectorResource uc16_resource_; +}; + + +// Test that a regular expression execution can be interrupted and +// the string changed without failing. +TEST(RegExpStringModification) { + v8::Locker lock; + v8::V8::Initialize(); + v8::HandleScope scope; + Local<Context> local_env; + { + LocalContext env; + local_env = env.local(); + } + + // Local context should still be live. + CHECK(!local_env.IsEmpty()); + local_env->Enter(); + + // Should complete without problems. + RegExpStringModificationTest().RunTest(); + + local_env->Exit(); +} + + +// Test that we can set a property on the global object even if there +// is a read-only property in the prototype chain. +TEST(ReadOnlyPropertyInGlobalProto) { + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> templ = v8::ObjectTemplate::New(); + LocalContext context(0, templ); + v8::Handle<v8::Object> global = context->Global(); + v8::Handle<v8::Object> global_proto = + v8::Handle<v8::Object>::Cast(global->Get(v8_str("__proto__"))); + global_proto->Set(v8_str("x"), v8::Integer::New(0), v8::ReadOnly); + global_proto->Set(v8_str("y"), v8::Integer::New(0), v8::ReadOnly); + // Check without 'eval' or 'with'. + v8::Handle<v8::Value> res = + CompileRun("function f() { x = 42; return x; }; f()"); + // Check with 'eval'. + res = CompileRun("function f() { eval('1'); y = 42; return y; }; f()"); + CHECK_EQ(v8::Integer::New(42), res); + // Check with 'with'. + res = CompileRun("function f() { with (this) { y = 42 }; return y; }; f()"); + CHECK_EQ(v8::Integer::New(42), res); +} diff --git a/v8/test/cctest/test-assembler-arm.cc b/v8/test/cctest/test-assembler-arm.cc new file mode 100644 index 0000000..bd75a04 --- /dev/null +++ b/v8/test/cctest/test-assembler-arm.cc @@ -0,0 +1,227 @@ +// Copyright 2006-2008 the V8 project authors. 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. + +#include "v8.h" + +#include "disassembler.h" +#include "factory.h" +#include "simulator-arm.h" +#include "assembler-arm-inl.h" +#include "cctest.h" + +using namespace v8::internal; + + +// Define these function prototypes to match JSEntryFunction in execution.cc. +typedef int (*F1)(int x, int p1, int p2, int p3, int p4); +typedef int (*F2)(int x, int y, int p2, int p3, int p4); +typedef int (*F3)(void* p, int p1, int p2, int p3, int p4); + + +static v8::Persistent<v8::Context> env; + + +// The test framework does not accept flags on the command line, so we set them +static void InitializeVM() { + // disable compilation of natives by specifying an empty natives file + FLAG_natives_file = ""; + + // enable generation of comments + FLAG_debug_code = true; + + if (env.IsEmpty()) { + env = v8::Context::New(); + } +} + + +#define __ assm. + +TEST(0) { + InitializeVM(); + v8::HandleScope scope; + + Assembler assm(NULL, 0); + + __ add(r0, r0, Operand(r1)); + __ mov(pc, Operand(lr)); + + CodeDesc desc; + assm.GetCode(&desc); + Object* code = Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value())); + CHECK(code->IsCode()); +#ifdef DEBUG + Code::cast(code)->Print(); +#endif + F2 f = FUNCTION_CAST<F2>(Code::cast(code)->entry()); + int res = reinterpret_cast<int>(CALL_GENERATED_CODE(f, 3, 4, 0, 0, 0)); + ::printf("f() = %d\n", res); + CHECK_EQ(7, res); +} + + +TEST(1) { + InitializeVM(); + v8::HandleScope scope; + + Assembler assm(NULL, 0); + Label L, C; + + __ mov(r1, Operand(r0)); + __ mov(r0, Operand(0)); + __ b(&C); + + __ bind(&L); + __ add(r0, r0, Operand(r1)); + __ sub(r1, r1, Operand(1)); + + __ bind(&C); + __ teq(r1, Operand(0)); + __ b(ne, &L); + __ mov(pc, Operand(lr)); + + CodeDesc desc; + assm.GetCode(&desc); + Object* code = Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value())); + CHECK(code->IsCode()); +#ifdef DEBUG + Code::cast(code)->Print(); +#endif + F1 f = FUNCTION_CAST<F1>(Code::cast(code)->entry()); + int res = reinterpret_cast<int>(CALL_GENERATED_CODE(f, 100, 0, 0, 0, 0)); + ::printf("f() = %d\n", res); + CHECK_EQ(5050, res); +} + + +TEST(2) { + InitializeVM(); + v8::HandleScope scope; + + Assembler assm(NULL, 0); + Label L, C; + + __ mov(r1, Operand(r0)); + __ mov(r0, Operand(1)); + __ b(&C); + + __ bind(&L); + __ mul(r0, r1, r0); + __ sub(r1, r1, Operand(1)); + + __ bind(&C); + __ teq(r1, Operand(0)); + __ b(ne, &L); + __ mov(pc, Operand(lr)); + + // some relocated stuff here, not executed + __ RecordComment("dead code, just testing relocations"); + __ mov(r0, Operand(Factory::true_value())); + __ RecordComment("dead code, just testing immediate operands"); + __ mov(r0, Operand(-1)); + __ mov(r0, Operand(0xFF000000)); + __ mov(r0, Operand(0xF0F0F0F0)); + __ mov(r0, Operand(0xFFF0FFFF)); + + CodeDesc desc; + assm.GetCode(&desc); + Object* code = Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value())); + CHECK(code->IsCode()); +#ifdef DEBUG + Code::cast(code)->Print(); +#endif + F1 f = FUNCTION_CAST<F1>(Code::cast(code)->entry()); + int res = reinterpret_cast<int>(CALL_GENERATED_CODE(f, 10, 0, 0, 0, 0)); + ::printf("f() = %d\n", res); + CHECK_EQ(3628800, res); +} + + +TEST(3) { + InitializeVM(); + v8::HandleScope scope; + + typedef struct { + int i; + char c; + int16_t s; + } T; + T t; + + Assembler assm(NULL, 0); + Label L, C; + + __ mov(ip, Operand(sp)); + __ stm(db_w, sp, r4.bit() | fp.bit() | sp.bit() | lr.bit()); + __ sub(fp, ip, Operand(4)); + __ mov(r4, Operand(r0)); + __ ldr(r0, MemOperand(r4, OFFSET_OF(T, i))); + __ mov(r2, Operand(r0, ASR, 1)); + __ str(r2, MemOperand(r4, OFFSET_OF(T, i))); + __ ldrsb(r2, MemOperand(r4, OFFSET_OF(T, c))); + __ add(r0, r2, Operand(r0)); + __ mov(r2, Operand(r2, LSL, 2)); + __ strb(r2, MemOperand(r4, OFFSET_OF(T, c))); + __ ldrsh(r2, MemOperand(r4, OFFSET_OF(T, s))); + __ add(r0, r2, Operand(r0)); + __ mov(r2, Operand(r2, ASR, 3)); + __ strh(r2, MemOperand(r4, OFFSET_OF(T, s))); + __ ldm(ia, sp, r4.bit() | fp.bit() | sp.bit() | pc.bit()); + + CodeDesc desc; + assm.GetCode(&desc); + Object* code = Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value())); + CHECK(code->IsCode()); +#ifdef DEBUG + Code::cast(code)->Print(); +#endif + F3 f = FUNCTION_CAST<F3>(Code::cast(code)->entry()); + t.i = 100000; + t.c = 10; + t.s = 1000; + int res = reinterpret_cast<int>(CALL_GENERATED_CODE(f, &t, 0, 0, 0, 0)); + ::printf("f() = %d\n", res); + CHECK_EQ(101010, res); + CHECK_EQ(100000/2, t.i); + CHECK_EQ(10*4, t.c); + CHECK_EQ(1000/8, t.s); +} + + +#undef __ diff --git a/v8/test/cctest/test-assembler-ia32.cc b/v8/test/cctest/test-assembler-ia32.cc new file mode 100644 index 0000000..9ad7c76 --- /dev/null +++ b/v8/test/cctest/test-assembler-ia32.cc @@ -0,0 +1,395 @@ +// Copyright 2006-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" + +#include "disassembler.h" +#include "factory.h" +#include "macro-assembler.h" +#include "platform.h" +#include "serialize.h" +#include "cctest.h" + +using namespace v8::internal; + + +typedef int (*F0)(); +typedef int (*F1)(int x); +typedef int (*F2)(int x, int y); + + +static v8::Persistent<v8::Context> env; + + +static void InitializeVM() { + if (env.IsEmpty()) { + env = v8::Context::New(); + } +} + + +#define __ assm. + +TEST(AssemblerIa320) { + InitializeVM(); + v8::HandleScope scope; + + v8::internal::byte buffer[256]; + Assembler assm(buffer, sizeof buffer); + + __ mov(eax, Operand(esp, 4)); + __ add(eax, Operand(esp, 8)); + __ ret(0); + + CodeDesc desc; + assm.GetCode(&desc); + Object* code = Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value())); + CHECK(code->IsCode()); +#ifdef DEBUG + Code::cast(code)->Print(); +#endif + F2 f = FUNCTION_CAST<F2>(Code::cast(code)->entry()); + int res = f(3, 4); + ::printf("f() = %d\n", res); + CHECK_EQ(7, res); +} + + +TEST(AssemblerIa321) { + InitializeVM(); + v8::HandleScope scope; + + v8::internal::byte buffer[256]; + Assembler assm(buffer, sizeof buffer); + Label L, C; + + __ mov(edx, Operand(esp, 4)); + __ xor_(eax, Operand(eax)); // clear eax + __ jmp(&C); + + __ bind(&L); + __ add(eax, Operand(edx)); + __ sub(Operand(edx), Immediate(1)); + + __ bind(&C); + __ test(edx, Operand(edx)); + __ j(not_zero, &L, taken); + __ ret(0); + + CodeDesc desc; + assm.GetCode(&desc); + Object* code = Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value())); + CHECK(code->IsCode()); +#ifdef DEBUG + Code::cast(code)->Print(); +#endif + F1 f = FUNCTION_CAST<F1>(Code::cast(code)->entry()); + int res = f(100); + ::printf("f() = %d\n", res); + CHECK_EQ(5050, res); +} + + +TEST(AssemblerIa322) { + InitializeVM(); + v8::HandleScope scope; + + v8::internal::byte buffer[256]; + Assembler assm(buffer, sizeof buffer); + Label L, C; + + __ mov(edx, Operand(esp, 4)); + __ mov(eax, 1); + __ jmp(&C); + + __ bind(&L); + __ imul(eax, Operand(edx)); + __ sub(Operand(edx), Immediate(1)); + + __ bind(&C); + __ test(edx, Operand(edx)); + __ j(not_zero, &L, taken); + __ ret(0); + + // some relocated stuff here, not executed + __ mov(eax, Factory::true_value()); + __ jmp(NULL, RelocInfo::RUNTIME_ENTRY); + + CodeDesc desc; + assm.GetCode(&desc); + Object* code = Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value())); + CHECK(code->IsCode()); +#ifdef DEBUG + Code::cast(code)->Print(); +#endif + F1 f = FUNCTION_CAST<F1>(Code::cast(code)->entry()); + int res = f(10); + ::printf("f() = %d\n", res); + CHECK_EQ(3628800, res); +} + + +typedef int (*F3)(float x); + +TEST(AssemblerIa323) { + InitializeVM(); + v8::HandleScope scope; + + v8::internal::byte buffer[256]; + Assembler assm(buffer, sizeof buffer); + + CHECK(CpuFeatures::IsSupported(CpuFeatures::SSE2)); + { CpuFeatures::Scope fscope(CpuFeatures::SSE2); + __ cvttss2si(eax, Operand(esp, 4)); + __ ret(0); + } + + CodeDesc desc; + assm.GetCode(&desc); + Code* code = + Code::cast(Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value()))); + // don't print the code - our disassembler can't handle cvttss2si + // instead print bytes + Disassembler::Dump(stdout, + code->instruction_start(), + code->instruction_start() + code->instruction_size()); + F3 f = FUNCTION_CAST<F3>(code->entry()); + int res = f(static_cast<float>(-3.1415)); + ::printf("f() = %d\n", res); + CHECK_EQ(-3, res); +} + + +typedef int (*F4)(double x); + +TEST(AssemblerIa324) { + InitializeVM(); + v8::HandleScope scope; + + v8::internal::byte buffer[256]; + Assembler assm(buffer, sizeof buffer); + + CHECK(CpuFeatures::IsSupported(CpuFeatures::SSE2)); + CpuFeatures::Scope fscope(CpuFeatures::SSE2); + __ cvttsd2si(eax, Operand(esp, 4)); + __ ret(0); + + CodeDesc desc; + assm.GetCode(&desc); + Code* code = + Code::cast(Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value()))); + // don't print the code - our disassembler can't handle cvttsd2si + // instead print bytes + Disassembler::Dump(stdout, + code->instruction_start(), + code->instruction_start() + code->instruction_size()); + F4 f = FUNCTION_CAST<F4>(code->entry()); + int res = f(2.718281828); + ::printf("f() = %d\n", res); + CHECK_EQ(2, res); +} + + +static int baz = 42; +TEST(AssemblerIa325) { + InitializeVM(); + v8::HandleScope scope; + + v8::internal::byte buffer[256]; + Assembler assm(buffer, sizeof buffer); + + __ mov(eax, Operand(reinterpret_cast<intptr_t>(&baz), RelocInfo::NONE)); + __ ret(0); + + CodeDesc desc; + assm.GetCode(&desc); + Code* code = + Code::cast(Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value()))); + F0 f = FUNCTION_CAST<F0>(code->entry()); + int res = f(); + CHECK_EQ(42, res); +} + + +typedef double (*F5)(double x, double y); + +TEST(AssemblerIa326) { + InitializeVM(); + v8::HandleScope scope; + CHECK(CpuFeatures::IsSupported(CpuFeatures::SSE2)); + CpuFeatures::Scope fscope(CpuFeatures::SSE2); + v8::internal::byte buffer[256]; + Assembler assm(buffer, sizeof buffer); + + __ movdbl(xmm0, Operand(esp, 1 * kPointerSize)); + __ movdbl(xmm1, Operand(esp, 3 * kPointerSize)); + __ addsd(xmm0, xmm1); + __ mulsd(xmm0, xmm1); + __ subsd(xmm0, xmm1); + __ divsd(xmm0, xmm1); + // Copy xmm0 to st(0) using eight bytes of stack. + __ sub(Operand(esp), Immediate(8)); + __ movdbl(Operand(esp, 0), xmm0); + __ fld_d(Operand(esp, 0)); + __ add(Operand(esp), Immediate(8)); + __ ret(0); + + CodeDesc desc; + assm.GetCode(&desc); + Code* code = + Code::cast(Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value()))); +#ifdef DEBUG + ::printf("\n---\n"); + // don't print the code - our disassembler can't handle SSE instructions + // instead print bytes + Disassembler::Dump(stdout, + code->instruction_start(), + code->instruction_start() + code->instruction_size()); +#endif + F5 f = FUNCTION_CAST<F5>(code->entry()); + double res = f(2.2, 1.1); + ::printf("f() = %f\n", res); + CHECK(2.29 < res && res < 2.31); +} + + +typedef double (*F6)(int x); + +TEST(AssemblerIa328) { + InitializeVM(); + v8::HandleScope scope; + CHECK(CpuFeatures::IsSupported(CpuFeatures::SSE2)); + CpuFeatures::Scope fscope(CpuFeatures::SSE2); + v8::internal::byte buffer[256]; + Assembler assm(buffer, sizeof buffer); + __ mov(eax, Operand(esp, 4)); + __ cvtsi2sd(xmm0, Operand(eax)); + // Copy xmm0 to st(0) using eight bytes of stack. + __ sub(Operand(esp), Immediate(8)); + __ movdbl(Operand(esp, 0), xmm0); + __ fld_d(Operand(esp, 0)); + __ add(Operand(esp), Immediate(8)); + __ ret(0); + CodeDesc desc; + assm.GetCode(&desc); + Code* code = + Code::cast(Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value()))); + CHECK(code->IsCode()); +#ifdef DEBUG + Code::cast(code)->Print(); +#endif + F6 f = FUNCTION_CAST<F6>(Code::cast(code)->entry()); + double res = f(12); + + ::printf("f() = %f\n", res); + CHECK(11.99 < res && res < 12.001); +} + + +typedef int (*F7)(double x, double y); + +TEST(AssemblerIa329) { + InitializeVM(); + v8::HandleScope scope; + v8::internal::byte buffer[256]; + MacroAssembler assm(buffer, sizeof buffer); + enum { kEqual = 0, kGreater = 1, kLess = 2, kNaN = 3, kUndefined = 4 }; + Label equal_l, less_l, greater_l, nan_l; + __ fld_d(Operand(esp, 3 * kPointerSize)); + __ fld_d(Operand(esp, 1 * kPointerSize)); + __ FCmp(); + __ j(parity_even, &nan_l, taken); + __ j(equal, &equal_l, taken); + __ j(below, &less_l, taken); + __ j(above, &greater_l, taken); + + __ mov(eax, kUndefined); + __ ret(0); + + __ bind(&equal_l); + __ mov(eax, kEqual); + __ ret(0); + + __ bind(&greater_l); + __ mov(eax, kGreater); + __ ret(0); + + __ bind(&less_l); + __ mov(eax, kLess); + __ ret(0); + + __ bind(&nan_l); + __ mov(eax, kNaN); + __ ret(0); + + + CodeDesc desc; + assm.GetCode(&desc); + Code* code = + Code::cast(Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value()))); + CHECK(code->IsCode()); +#ifdef DEBUG + Code::cast(code)->Print(); +#endif + + F7 f = FUNCTION_CAST<F7>(Code::cast(code)->entry()); + CHECK_EQ(kLess, f(1.1, 2.2)); + CHECK_EQ(kEqual, f(2.2, 2.2)); + CHECK_EQ(kGreater, f(3.3, 2.2)); + CHECK_EQ(kNaN, f(OS::nan_value(), 1.1)); +} + +#undef __ diff --git a/v8/test/cctest/test-ast.cc b/v8/test/cctest/test-ast.cc new file mode 100644 index 0000000..2054348 --- /dev/null +++ b/v8/test/cctest/test-ast.cc @@ -0,0 +1,97 @@ +// Copyright 2006-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" + +#include "ast.h" +#include "cctest.h" + +using namespace v8::internal; + +TEST(List) { + List<Node*>* list = new List<Node*>(0); + CHECK_EQ(0, list->length()); + + ZoneScope zone_scope(DELETE_ON_EXIT); + Node* node = new EmptyStatement(); + list->Add(node); + CHECK_EQ(1, list->length()); + CHECK_EQ(node, list->at(0)); + CHECK_EQ(node, list->last()); + + const int kElements = 100; + for (int i = 0; i < kElements; i++) { + list->Add(node); + } + CHECK_EQ(1 + kElements, list->length()); + + list->Clear(); + CHECK_EQ(0, list->length()); + delete list; +} + + +TEST(RemoveLast) { + List<int> list(4); + CHECK_EQ(0, list.length()); + list.Add(1); + CHECK_EQ(1, list.length()); + CHECK_EQ(1, list.last()); + list.RemoveLast(); + CHECK_EQ(0, list.length()); + list.Add(2); + list.Add(3); + CHECK_EQ(2, list.length()); + CHECK_EQ(3, list.last()); + list.RemoveLast(); + CHECK_EQ(1, list.length()); + CHECK_EQ(2, list.last()); + list.RemoveLast(); + CHECK_EQ(0, list.length()); + + const int kElements = 100; + for (int i = 0; i < kElements; i++) list.Add(i); + for (int j = kElements - 1; j >= 0; j--) { + CHECK_EQ(j + 1, list.length()); + CHECK_EQ(j, list.last()); + list.RemoveLast(); + CHECK_EQ(j, list.length()); + } +} + + +TEST(DeleteEmpty) { + { + List<int>* list = new List<int>(0); + delete list; + } + { + List<int> list(0); + } +} diff --git a/v8/test/cctest/test-compiler.cc b/v8/test/cctest/test-compiler.cc new file mode 100644 index 0000000..08037b3 --- /dev/null +++ b/v8/test/cctest/test-compiler.cc @@ -0,0 +1,318 @@ +// Copyright 2006-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" + +#include "compiler.h" +#include "execution.h" +#include "factory.h" +#include "platform.h" +#include "top.h" +#include "cctest.h" + +using namespace v8::internal; + +static v8::Persistent<v8::Context> env; + +// --- P r i n t E x t e n s i o n --- + +class PrintExtension : public v8::Extension { + public: + PrintExtension() : v8::Extension("v8/print", kSource) { } + virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( + v8::Handle<v8::String> name); + static v8::Handle<v8::Value> Print(const v8::Arguments& args); + private: + static const char* kSource; +}; + + +const char* PrintExtension::kSource = "native function print();"; + + +v8::Handle<v8::FunctionTemplate> PrintExtension::GetNativeFunction( + v8::Handle<v8::String> str) { + return v8::FunctionTemplate::New(PrintExtension::Print); +} + + +v8::Handle<v8::Value> PrintExtension::Print(const v8::Arguments& args) { + for (int i = 0; i < args.Length(); i++) { + if (i != 0) printf(" "); + v8::HandleScope scope; + v8::Handle<v8::Value> arg = args[i]; + v8::Handle<v8::String> string_obj = arg->ToString(); + if (string_obj.IsEmpty()) return string_obj; + int length = string_obj->Length(); + uint16_t* string = NewArray<uint16_t>(length + 1); + string_obj->Write(string); + for (int j = 0; j < length; j++) + printf("%lc", string[j]); + DeleteArray(string); + } + printf("\n"); + return v8::Undefined(); +} + + +static PrintExtension kPrintExtension; +v8::DeclareExtension kPrintExtensionDeclaration(&kPrintExtension); + + +static void InitializeVM() { + if (env.IsEmpty()) { + v8::HandleScope scope; + const char* extensions[] = { "v8/print", "v8/gc" }; + v8::ExtensionConfiguration config(2, extensions); + env = v8::Context::New(&config); + } + v8::HandleScope scope; + env->Enter(); +} + + +static Object* GetGlobalProperty(const char* name) { + Handle<String> symbol = Factory::LookupAsciiSymbol(name); + return Top::context()->global()->GetProperty(*symbol); +} + + +static void SetGlobalProperty(const char* name, Object* value) { + Handle<Object> object(value); + Handle<String> symbol = Factory::LookupAsciiSymbol(name); + Handle<JSObject> global(Top::context()->global()); + SetProperty(global, symbol, object, NONE); +} + + +static Handle<JSFunction> Compile(const char* source) { + Handle<String> source_code(Factory::NewStringFromUtf8(CStrVector(source))); + Handle<JSFunction> boilerplate = + Compiler::Compile(source_code, Handle<String>(), 0, 0, NULL, NULL); + return Factory::NewFunctionFromBoilerplate(boilerplate, + Top::global_context()); +} + + +static double Inc(int x) { + const char* source = "result = %d + 1;"; + EmbeddedVector<char, 512> buffer; + OS::SNPrintF(buffer, source, x); + + Handle<JSFunction> fun = Compile(buffer.start()); + if (fun.is_null()) return -1; + + bool has_pending_exception; + Handle<JSObject> global(Top::context()->global()); + Execution::Call(fun, global, 0, NULL, &has_pending_exception); + CHECK(!has_pending_exception); + return GetGlobalProperty("result")->Number(); +} + + +TEST(Inc) { + InitializeVM(); + v8::HandleScope scope; + CHECK_EQ(4.0, Inc(3)); +} + + +static double Add(int x, int y) { + Handle<JSFunction> fun = Compile("result = x + y;"); + if (fun.is_null()) return -1; + + SetGlobalProperty("x", Smi::FromInt(x)); + SetGlobalProperty("y", Smi::FromInt(y)); + bool has_pending_exception; + Handle<JSObject> global(Top::context()->global()); + Execution::Call(fun, global, 0, NULL, &has_pending_exception); + CHECK(!has_pending_exception); + return GetGlobalProperty("result")->Number(); +} + + +TEST(Add) { + InitializeVM(); + v8::HandleScope scope; + CHECK_EQ(5.0, Add(2, 3)); +} + + +static double Abs(int x) { + Handle<JSFunction> fun = Compile("if (x < 0) result = -x; else result = x;"); + if (fun.is_null()) return -1; + + SetGlobalProperty("x", Smi::FromInt(x)); + bool has_pending_exception; + Handle<JSObject> global(Top::context()->global()); + Execution::Call(fun, global, 0, NULL, &has_pending_exception); + CHECK(!has_pending_exception); + return GetGlobalProperty("result")->Number(); +} + + +TEST(Abs) { + InitializeVM(); + v8::HandleScope scope; + CHECK_EQ(3.0, Abs(-3)); +} + + +static double Sum(int n) { + Handle<JSFunction> fun = + Compile("s = 0; while (n > 0) { s += n; n -= 1; }; result = s;"); + if (fun.is_null()) return -1; + + SetGlobalProperty("n", Smi::FromInt(n)); + bool has_pending_exception; + Handle<JSObject> global(Top::context()->global()); + Execution::Call(fun, global, 0, NULL, &has_pending_exception); + CHECK(!has_pending_exception); + return GetGlobalProperty("result")->Number(); +} + + +TEST(Sum) { + InitializeVM(); + v8::HandleScope scope; + CHECK_EQ(5050.0, Sum(100)); +} + + +TEST(Print) { + InitializeVM(); + v8::HandleScope scope; + const char* source = "for (n = 0; n < 100; ++n) print(n, 1, 2);"; + Handle<JSFunction> fun = Compile(source); + if (fun.is_null()) return; + bool has_pending_exception; + Handle<JSObject> global(Top::context()->global()); + Execution::Call(fun, global, 0, NULL, &has_pending_exception); + CHECK(!has_pending_exception); +} + + +// The following test method stems from my coding efforts today. It +// tests all the functionality I have added to the compiler today +TEST(Stuff) { + InitializeVM(); + v8::HandleScope scope; + const char* source = + "r = 0;\n" + "a = new Object;\n" + "if (a == a) r+=1;\n" // 1 + "if (a != new Object()) r+=2;\n" // 2 + "a.x = 42;\n" + "if (a.x == 42) r+=4;\n" // 4 + "function foo() { var x = 87; return x; }\n" + "if (foo() == 87) r+=8;\n" // 8 + "function bar() { var x; x = 99; return x; }\n" + "if (bar() == 99) r+=16;\n" // 16 + "function baz() { var x = 1, y, z = 2; y = 3; return x + y + z; }\n" + "if (baz() == 6) r+=32;\n" // 32 + "function Cons0() { this.x = 42; this.y = 87; }\n" + "if (new Cons0().x == 42) r+=64;\n" // 64 + "if (new Cons0().y == 87) r+=128;\n" // 128 + "function Cons2(x, y) { this.sum = x + y; }\n" + "if (new Cons2(3,4).sum == 7) r+=256;"; // 256 + + Handle<JSFunction> fun = Compile(source); + CHECK(!fun.is_null()); + bool has_pending_exception; + Handle<JSObject> global(Top::context()->global()); + Execution::Call(fun, global, 0, NULL, &has_pending_exception); + CHECK(!has_pending_exception); + CHECK_EQ(511.0, GetGlobalProperty("r")->Number()); +} + + +TEST(UncaughtThrow) { + InitializeVM(); + v8::HandleScope scope; + + const char* source = "throw 42;"; + Handle<JSFunction> fun = Compile(source); + CHECK(!fun.is_null()); + bool has_pending_exception; + Handle<JSObject> global(Top::context()->global()); + Handle<Object> result = + Execution::Call(fun, global, 0, NULL, &has_pending_exception); + CHECK(has_pending_exception); + CHECK_EQ(42.0, Top::pending_exception()->Number()); +} + + +// Tests calling a builtin function from C/C++ code, and the builtin function +// performs GC. It creates a stack frame looks like following: +// | C (PerformGC) | +// | JS-to-C | +// | JS | +// | C-to-JS | +TEST(C2JSFrames) { + InitializeVM(); + v8::HandleScope scope; + + const char* source = "function foo(a) { gc(), print(a); }"; + + Handle<JSFunction> fun0 = Compile(source); + CHECK(!fun0.is_null()); + + // Run the generated code to populate the global object with 'foo'. + bool has_pending_exception; + Handle<JSObject> global(Top::context()->global()); + Execution::Call(fun0, global, 0, NULL, &has_pending_exception); + CHECK(!has_pending_exception); + + Handle<Object> fun1 = + Handle<Object>( + Top::context()->global()->GetProperty( + *Factory::LookupAsciiSymbol("foo"))); + CHECK(fun1->IsJSFunction()); + + Object** argv[1] = { + Handle<Object>::cast(Factory::LookupAsciiSymbol("hello")).location() + }; + Execution::Call(Handle<JSFunction>::cast(fun1), global, 1, argv, + &has_pending_exception); + CHECK(!has_pending_exception); +} + + +// Regression 236. Calling InitLineEnds on a Script with undefined +// source resulted in crash. +TEST(Regression236) { + InitializeVM(); + v8::HandleScope scope; + + Handle<Script> script = Factory::NewScript(Factory::empty_string()); + script->set_source(Heap::undefined_value()); + CHECK_EQ(-1, GetScriptLineNumber(script, 0)); + CHECK_EQ(-1, GetScriptLineNumber(script, 100)); + CHECK_EQ(-1, GetScriptLineNumber(script, -1)); +} diff --git a/v8/test/cctest/test-conversions.cc b/v8/test/cctest/test-conversions.cc new file mode 100644 index 0000000..6c0b9a6 --- /dev/null +++ b/v8/test/cctest/test-conversions.cc @@ -0,0 +1,130 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. + +#include <stdlib.h> + +#include "v8.h" + +#include "platform.h" +#include "cctest.h" + +using namespace v8::internal; + + +TEST(Hex) { + CHECK_EQ(0.0, StringToDouble("0x0", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(0.0, StringToDouble("0X0", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(1.0, StringToDouble("0x1", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(16.0, StringToDouble("0x10", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(255.0, StringToDouble("0xff", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(175.0, StringToDouble("0xAF", ALLOW_HEX | ALLOW_OCTALS)); + + CHECK_EQ(0.0, StringToDouble("0x0", ALLOW_HEX)); + CHECK_EQ(0.0, StringToDouble("0X0", ALLOW_HEX)); + CHECK_EQ(1.0, StringToDouble("0x1", ALLOW_HEX)); + CHECK_EQ(16.0, StringToDouble("0x10", ALLOW_HEX)); + CHECK_EQ(255.0, StringToDouble("0xff", ALLOW_HEX)); + CHECK_EQ(175.0, StringToDouble("0xAF", ALLOW_HEX)); +} + + +TEST(Octal) { + CHECK_EQ(0.0, StringToDouble("0", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(0.0, StringToDouble("00", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(1.0, StringToDouble("01", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(7.0, StringToDouble("07", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(8.0, StringToDouble("010", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(63.0, StringToDouble("077", ALLOW_HEX | ALLOW_OCTALS)); + + CHECK_EQ(0.0, StringToDouble("0", ALLOW_HEX)); + CHECK_EQ(0.0, StringToDouble("00", ALLOW_HEX)); + CHECK_EQ(1.0, StringToDouble("01", ALLOW_HEX)); + CHECK_EQ(7.0, StringToDouble("07", ALLOW_HEX)); + CHECK_EQ(10.0, StringToDouble("010", ALLOW_HEX)); + CHECK_EQ(77.0, StringToDouble("077", ALLOW_HEX)); +} + + +TEST(MalformedOctal) { + CHECK_EQ(8.0, StringToDouble("08", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(81.0, StringToDouble("081", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(78.0, StringToDouble("078", ALLOW_HEX | ALLOW_OCTALS)); + + CHECK(isnan(StringToDouble("07.7", ALLOW_HEX | ALLOW_OCTALS))); + CHECK(isnan(StringToDouble("07.8", ALLOW_HEX | ALLOW_OCTALS))); + CHECK(isnan(StringToDouble("07e8", ALLOW_HEX | ALLOW_OCTALS))); + CHECK(isnan(StringToDouble("07e7", ALLOW_HEX | ALLOW_OCTALS))); + + CHECK_EQ(8.7, StringToDouble("08.7", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(8e7, StringToDouble("08e7", ALLOW_HEX | ALLOW_OCTALS)); + + CHECK_EQ(0.001, StringToDouble("0.001", ALLOW_HEX | ALLOW_OCTALS)); + CHECK_EQ(0.713, StringToDouble("0.713", ALLOW_HEX | ALLOW_OCTALS)); + + CHECK_EQ(8.0, StringToDouble("08", ALLOW_HEX)); + CHECK_EQ(81.0, StringToDouble("081", ALLOW_HEX)); + CHECK_EQ(78.0, StringToDouble("078", ALLOW_HEX)); + + CHECK_EQ(7.7, StringToDouble("07.7", ALLOW_HEX)); + CHECK_EQ(7.8, StringToDouble("07.8", ALLOW_HEX)); + CHECK_EQ(7e8, StringToDouble("07e8", ALLOW_HEX)); + CHECK_EQ(7e7, StringToDouble("07e7", ALLOW_HEX)); + + CHECK_EQ(8.7, StringToDouble("08.7", ALLOW_HEX)); + CHECK_EQ(8e7, StringToDouble("08e7", ALLOW_HEX)); + + CHECK_EQ(0.001, StringToDouble("0.001", ALLOW_HEX)); + CHECK_EQ(0.713, StringToDouble("0.713", ALLOW_HEX)); +} + + +TEST(TrailingJunk) { + CHECK_EQ(8.0, StringToDouble("8q", ALLOW_TRAILING_JUNK)); + CHECK_EQ(63.0, StringToDouble("077qqq", ALLOW_OCTALS | ALLOW_TRAILING_JUNK)); +} + + +TEST(NonStrDecimalLiteral) { + CHECK(isnan(StringToDouble(" ", NO_FLAGS, OS::nan_value()))); + CHECK(isnan(StringToDouble("", NO_FLAGS, OS::nan_value()))); + CHECK(isnan(StringToDouble(" ", NO_FLAGS, OS::nan_value()))); + CHECK_EQ(0.0, StringToDouble("", NO_FLAGS)); + CHECK_EQ(0.0, StringToDouble(" ", NO_FLAGS)); +} + + +TEST(BitField) { + uint32_t x; + + // One bit bit field can hold values 0 and 1. + class OneBit1: public BitField<uint32_t, 0, 1> {}; + class OneBit2: public BitField<uint32_t, 7, 1> {}; + CHECK(!OneBit1::is_valid(static_cast<uint32_t>(-1))); + CHECK(!OneBit2::is_valid(static_cast<uint32_t>(-1))); + for (int i = 0; i < 2; i++) { + CHECK(OneBit1::is_valid(i)); + x = OneBit1::encode(i); + CHECK_EQ(i, OneBit1::decode(x)); + + CHECK(OneBit2::is_valid(i)); + x = OneBit2::encode(i); + CHECK_EQ(i, OneBit2::decode(x)); + } + CHECK(!OneBit1::is_valid(2)); + CHECK(!OneBit2::is_valid(2)); + + // Eight bit bit field can hold values from 0 tp 255. + class EightBit1: public BitField<uint32_t, 0, 8> {}; + class EightBit2: public BitField<uint32_t, 13, 8> {}; + CHECK(!EightBit1::is_valid(static_cast<uint32_t>(-1))); + CHECK(!EightBit2::is_valid(static_cast<uint32_t>(-1))); + for (int i = 0; i < 256; i++) { + CHECK(EightBit1::is_valid(i)); + x = EightBit1::encode(i); + CHECK_EQ(i, EightBit1::decode(x)); + CHECK(EightBit2::is_valid(i)); + x = EightBit2::encode(i); + CHECK_EQ(i, EightBit2::decode(x)); + } + CHECK(!EightBit1::is_valid(256)); + CHECK(!EightBit2::is_valid(256)); +} diff --git a/v8/test/cctest/test-debug.cc b/v8/test/cctest/test-debug.cc new file mode 100644 index 0000000..74d590e --- /dev/null +++ b/v8/test/cctest/test-debug.cc @@ -0,0 +1,4087 @@ +// Copyright 2007-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" + +#include "api.h" +#include "debug.h" +#include "platform.h" +#include "stub-cache.h" +#include "cctest.h" + + +using ::v8::internal::EmbeddedVector; +using ::v8::internal::Object; +using ::v8::internal::OS; +using ::v8::internal::Handle; +using ::v8::internal::Heap; +using ::v8::internal::JSGlobalProxy; +using ::v8::internal::Code; +using ::v8::internal::Debug; +using ::v8::internal::Debugger; +using ::v8::internal::StepAction; +using ::v8::internal::StepIn; // From StepAction enum +using ::v8::internal::StepNext; // From StepAction enum +using ::v8::internal::StepOut; // From StepAction enum + + +// Size of temp buffer for formatting small strings. +#define SMALL_STRING_BUFFER_SIZE 80 + +// --- A d d i t i o n a l C h e c k H e l p e r s + + +// Helper function used by the CHECK_EQ function when given Address +// arguments. Should not be called directly. +static inline void CheckEqualsHelper(const char* file, int line, + const char* expected_source, + ::v8::internal::Address expected, + const char* value_source, + ::v8::internal::Address value) { + if (expected != value) { + V8_Fatal(file, line, "CHECK_EQ(%s, %s) failed\n# " + "Expected: %i\n# Found: %i", + expected_source, value_source, expected, value); + } +} + + +// Helper function used by the CHECK_NE function when given Address +// arguments. Should not be called directly. +static inline void CheckNonEqualsHelper(const char* file, int line, + const char* unexpected_source, + ::v8::internal::Address unexpected, + const char* value_source, + ::v8::internal::Address value) { + if (unexpected == value) { + V8_Fatal(file, line, "CHECK_NE(%s, %s) failed\n# Value: %i", + unexpected_source, value_source, value); + } +} + + +// Helper function used by the CHECK function when given code +// arguments. Should not be called directly. +static inline void CheckEqualsHelper(const char* file, int line, + const char* expected_source, + const Code* expected, + const char* value_source, + const Code* value) { + if (expected != value) { + V8_Fatal(file, line, "CHECK_EQ(%s, %s) failed\n# " + "Expected: %p\n# Found: %p", + expected_source, value_source, expected, value); + } +} + + +static inline void CheckNonEqualsHelper(const char* file, int line, + const char* expected_source, + const Code* expected, + const char* value_source, + const Code* value) { + if (expected == value) { + V8_Fatal(file, line, "CHECK_NE(%s, %s) failed\n# Value: %p", + expected_source, value_source, value); + } +} + + +// --- H e l p e r C l a s s e s + + +// Helper class for creating a V8 enviromnent for running tests +class DebugLocalContext { + public: + inline DebugLocalContext( + v8::ExtensionConfiguration* extensions = 0, + v8::Handle<v8::ObjectTemplate> global_template = + v8::Handle<v8::ObjectTemplate>(), + v8::Handle<v8::Value> global_object = v8::Handle<v8::Value>()) + : context_(v8::Context::New(extensions, global_template, global_object)) { + context_->Enter(); + } + inline ~DebugLocalContext() { + context_->Exit(); + context_.Dispose(); + } + inline v8::Context* operator->() { return *context_; } + inline v8::Context* operator*() { return *context_; } + inline bool IsReady() { return !context_.IsEmpty(); } + void ExposeDebug() { + // Expose the debug context global object in the global object for testing. + Debug::Load(); + Debug::debug_context()->set_security_token( + v8::Utils::OpenHandle(*context_)->security_token()); + + Handle<JSGlobalProxy> global(Handle<JSGlobalProxy>::cast( + v8::Utils::OpenHandle(*context_->Global()))); + Handle<v8::internal::String> debug_string = + v8::internal::Factory::LookupAsciiSymbol("debug"); + SetProperty(global, debug_string, + Handle<Object>(Debug::debug_context()->global_proxy()), DONT_ENUM); + } + private: + v8::Persistent<v8::Context> context_; +}; + + +// --- H e l p e r F u n c t i o n s + + +// Compile and run the supplied source and return the fequested function. +static v8::Local<v8::Function> CompileFunction(DebugLocalContext* env, + const char* source, + const char* function_name) { + v8::Script::Compile(v8::String::New(source))->Run(); + return v8::Local<v8::Function>::Cast( + (*env)->Global()->Get(v8::String::New(function_name))); +} + +// Helper function that compiles and runs the source. +static v8::Local<v8::Value> CompileRun(const char* source) { + return v8::Script::Compile(v8::String::New(source))->Run(); +} + +// Is there any debug info for the function? +static bool HasDebugInfo(v8::Handle<v8::Function> fun) { + Handle<v8::internal::JSFunction> f = v8::Utils::OpenHandle(*fun); + Handle<v8::internal::SharedFunctionInfo> shared(f->shared()); + return Debug::HasDebugInfo(shared); +} + + +// Set a break point in a function and return the associated break point +// number. +static int SetBreakPoint(Handle<v8::internal::JSFunction> fun, int position) { + static int break_point = 0; + Handle<v8::internal::SharedFunctionInfo> shared(fun->shared()); + Debug::SetBreakPoint( + shared, position, + Handle<Object>(v8::internal::Smi::FromInt(++break_point))); + return break_point; +} + + +// Set a break point in a function and return the associated break point +// number. +static int SetBreakPoint(v8::Handle<v8::Function> fun, int position) { + return SetBreakPoint(v8::Utils::OpenHandle(*fun), position); +} + + +// Set a break point in a function using the Debug object and return the +// associated break point number. +static int SetBreakPointFromJS(const char* function_name, + int line, int position) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + OS::SNPrintF(buffer, + "debug.Debug.setBreakPoint(%s,%d,%d)", + function_name, line, position); + buffer[SMALL_STRING_BUFFER_SIZE - 1] = '\0'; + v8::Handle<v8::String> str = v8::String::New(buffer.start()); + return v8::Script::Compile(str)->Run()->Int32Value(); +} + + +// Set a break point in a script identified by id using the global Debug object. +static int SetScriptBreakPointByIdFromJS(int script_id, int line, int column) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + if (column >= 0) { + // Column specified set script break point on precise location. + OS::SNPrintF(buffer, + "debug.Debug.setScriptBreakPointById(%d,%d,%d)", + script_id, line, column); + } else { + // Column not specified set script break point on line. + OS::SNPrintF(buffer, + "debug.Debug.setScriptBreakPointById(%d,%d)", + script_id, line); + } + buffer[SMALL_STRING_BUFFER_SIZE - 1] = '\0'; + { + v8::TryCatch try_catch; + v8::Handle<v8::String> str = v8::String::New(buffer.start()); + v8::Handle<v8::Value> value = v8::Script::Compile(str)->Run(); + ASSERT(!try_catch.HasCaught()); + return value->Int32Value(); + } +} + + +// Set a break point in a script identified by name using the global Debug +// object. +static int SetScriptBreakPointByNameFromJS(const char* script_name, + int line, int column) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + if (column >= 0) { + // Column specified set script break point on precise location. + OS::SNPrintF(buffer, + "debug.Debug.setScriptBreakPointByName(\"%s\",%d,%d)", + script_name, line, column); + } else { + // Column not specified set script break point on line. + OS::SNPrintF(buffer, + "debug.Debug.setScriptBreakPointByName(\"%s\",%d)", + script_name, line); + } + buffer[SMALL_STRING_BUFFER_SIZE - 1] = '\0'; + { + v8::TryCatch try_catch; + v8::Handle<v8::String> str = v8::String::New(buffer.start()); + v8::Handle<v8::Value> value = v8::Script::Compile(str)->Run(); + ASSERT(!try_catch.HasCaught()); + return value->Int32Value(); + } +} + + +// Clear a break point. +static void ClearBreakPoint(int break_point) { + Debug::ClearBreakPoint( + Handle<Object>(v8::internal::Smi::FromInt(break_point))); +} + + +// Clear a break point using the global Debug object. +static void ClearBreakPointFromJS(int break_point_number) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + OS::SNPrintF(buffer, + "debug.Debug.clearBreakPoint(%d)", + break_point_number); + buffer[SMALL_STRING_BUFFER_SIZE - 1] = '\0'; + v8::Script::Compile(v8::String::New(buffer.start()))->Run(); +} + + +static void EnableScriptBreakPointFromJS(int break_point_number) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + OS::SNPrintF(buffer, + "debug.Debug.enableScriptBreakPoint(%d)", + break_point_number); + buffer[SMALL_STRING_BUFFER_SIZE - 1] = '\0'; + v8::Script::Compile(v8::String::New(buffer.start()))->Run(); +} + + +static void DisableScriptBreakPointFromJS(int break_point_number) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + OS::SNPrintF(buffer, + "debug.Debug.disableScriptBreakPoint(%d)", + break_point_number); + buffer[SMALL_STRING_BUFFER_SIZE - 1] = '\0'; + v8::Script::Compile(v8::String::New(buffer.start()))->Run(); +} + + +static void ChangeScriptBreakPointConditionFromJS(int break_point_number, + const char* condition) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + OS::SNPrintF(buffer, + "debug.Debug.changeScriptBreakPointCondition(%d, \"%s\")", + break_point_number, condition); + buffer[SMALL_STRING_BUFFER_SIZE - 1] = '\0'; + v8::Script::Compile(v8::String::New(buffer.start()))->Run(); +} + + +static void ChangeScriptBreakPointIgnoreCountFromJS(int break_point_number, + int ignoreCount) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + OS::SNPrintF(buffer, + "debug.Debug.changeScriptBreakPointIgnoreCount(%d, %d)", + break_point_number, ignoreCount); + buffer[SMALL_STRING_BUFFER_SIZE - 1] = '\0'; + v8::Script::Compile(v8::String::New(buffer.start()))->Run(); +} + + +// Change break on exception. +static void ChangeBreakOnException(bool caught, bool uncaught) { + Debug::ChangeBreakOnException(v8::internal::BreakException, caught); + Debug::ChangeBreakOnException(v8::internal::BreakUncaughtException, uncaught); +} + + +// Change break on exception using the global Debug object. +static void ChangeBreakOnExceptionFromJS(bool caught, bool uncaught) { + if (caught) { + v8::Script::Compile( + v8::String::New("debug.Debug.setBreakOnException()"))->Run(); + } else { + v8::Script::Compile( + v8::String::New("debug.Debug.clearBreakOnException()"))->Run(); + } + if (uncaught) { + v8::Script::Compile( + v8::String::New("debug.Debug.setBreakOnUncaughtException()"))->Run(); + } else { + v8::Script::Compile( + v8::String::New("debug.Debug.clearBreakOnUncaughtException()"))->Run(); + } +} + + +// Prepare to step to next break location. +static void PrepareStep(StepAction step_action) { + Debug::PrepareStep(step_action, 1); +} + + +// This function is in namespace v8::internal to be friend with class +// v8::internal::Debug. +namespace v8 { namespace internal { // NOLINT + +// Collect the currently debugged functions. +Handle<FixedArray> GetDebuggedFunctions() { + v8::internal::DebugInfoListNode* node = Debug::debug_info_list_; + + // Find the number of debugged functions. + int count = 0; + while (node) { + count++; + node = node->next(); + } + + // Allocate array for the debugged functions + Handle<FixedArray> debugged_functions = + v8::internal::Factory::NewFixedArray(count); + + // Run through the debug info objects and collect all functions. + count = 0; + while (node) { + debugged_functions->set(count++, *node->debug_info()); + node = node->next(); + } + + return debugged_functions; +} + + +static Handle<Code> ComputeCallDebugBreak(int argc) { + CALL_HEAP_FUNCTION(v8::internal::StubCache::ComputeCallDebugBreak(argc), + Code); +} + + +// Check that the debugger has been fully unloaded. +void CheckDebuggerUnloaded(bool check_functions) { + // Check that the debugger context is cleared and that there is no debug + // information stored for the debugger. + CHECK(Debug::debug_context().is_null()); + CHECK_EQ(NULL, Debug::debug_info_list_); + + // Collect garbage to ensure weak handles are cleared. + Heap::CollectAllGarbage(); + Heap::CollectAllGarbage(); + + // Iterate the head and check that there are no debugger related objects left. + HeapIterator iterator; + while (iterator.has_next()) { + HeapObject* obj = iterator.next(); + CHECK(obj != NULL); + CHECK(!obj->IsDebugInfo()); + CHECK(!obj->IsBreakPointInfo()); + + // If deep check of functions is requested check that no debug break code + // is left in all functions. + if (check_functions) { + if (obj->IsJSFunction()) { + JSFunction* fun = JSFunction::cast(obj); + for (RelocIterator it(fun->shared()->code()); !it.done(); it.next()) { + RelocInfo::Mode rmode = it.rinfo()->rmode(); + if (RelocInfo::IsCodeTarget(rmode)) { + CHECK(!Debug::IsDebugBreak(it.rinfo()->target_address())); + } else if (RelocInfo::IsJSReturn(rmode)) { + CHECK(!Debug::IsDebugBreakAtReturn(it.rinfo())); + } + } + } + } + } +} + + +} } // namespace v8::internal + + +// Check that the debugger has been fully unloaded. +static void CheckDebuggerUnloaded(bool check_functions = false) { + v8::internal::CheckDebuggerUnloaded(check_functions); +} + + +// Inherit from BreakLocationIterator to get access to protected parts for +// testing. +class TestBreakLocationIterator: public v8::internal::BreakLocationIterator { + public: + explicit TestBreakLocationIterator(Handle<v8::internal::DebugInfo> debug_info) + : BreakLocationIterator(debug_info, v8::internal::SOURCE_BREAK_LOCATIONS) {} + v8::internal::RelocIterator* it() { return reloc_iterator_; } + v8::internal::RelocIterator* it_original() { + return reloc_iterator_original_; + } +}; + + +// Compile a function, set a break point and check that the call at the break +// location in the code is the expected debug_break function. +void CheckDebugBreakFunction(DebugLocalContext* env, + const char* source, const char* name, + int position, v8::internal::RelocInfo::Mode mode, + Code* debug_break) { + // Create function and set the break point. + Handle<v8::internal::JSFunction> fun = v8::Utils::OpenHandle( + *CompileFunction(env, source, name)); + int bp = SetBreakPoint(fun, position); + + // Check that the debug break function is as expected. + Handle<v8::internal::SharedFunctionInfo> shared(fun->shared()); + CHECK(Debug::HasDebugInfo(shared)); + TestBreakLocationIterator it1(Debug::GetDebugInfo(shared)); + it1.FindBreakLocationFromPosition(position); + CHECK_EQ(mode, it1.it()->rinfo()->rmode()); + if (mode != v8::internal::RelocInfo::JS_RETURN) { + CHECK_EQ(debug_break, + Code::GetCodeFromTargetAddress(it1.it()->rinfo()->target_address())); + } else { + // TODO(1240753): Make the test architecture independent or split + // parts of the debugger into architecture dependent files. + CHECK_EQ(0xE8, *(it1.rinfo()->pc())); + } + + // Clear the break point and check that the debug break function is no longer + // there + ClearBreakPoint(bp); + CHECK(!Debug::HasDebugInfo(shared)); + CHECK(Debug::EnsureDebugInfo(shared)); + TestBreakLocationIterator it2(Debug::GetDebugInfo(shared)); + it2.FindBreakLocationFromPosition(position); + CHECK_EQ(mode, it2.it()->rinfo()->rmode()); + if (mode == v8::internal::RelocInfo::JS_RETURN) { + // TODO(1240753): Make the test architecture independent or split + // parts of the debugger into architecture dependent files. + CHECK_NE(0xE8, *(it2.rinfo()->pc())); + } +} + + +// --- D e b u g E v e n t H a n d l e r s +// --- +// --- The different tests uses a number of debug event handlers. +// --- + + +// Source for The JavaScript function which picks out the function name on the +// top frame. +const char* frame_function_name_source = + "function frame_function_name(exec_state) {" + " return exec_state.frame(0).func().name();" + "}"; +v8::Local<v8::Function> frame_function_name; + + +// Source for The JavaScript function which returns the number of frames. +static const char* frame_count_source = + "function frame_count(exec_state) {" + " return exec_state.frameCount();" + "}"; +v8::Handle<v8::Function> frame_count; + + +// Global variable to store the last function hit - used by some tests. +char last_function_hit[80]; + +// Debug event handler which counts the break points which have been hit. +int break_point_hit_count = 0; +static void DebugEventBreakPointHitCount(v8::DebugEvent event, + v8::Handle<v8::Object> exec_state, + v8::Handle<v8::Object> event_data, + v8::Handle<v8::Value> data) { + // When hitting a debug event listener there must be a break set. + CHECK_NE(v8::internal::Debug::break_id(), 0); + + // Count the number of breaks. + if (event == v8::Break) { + break_point_hit_count++; + if (!frame_function_name.IsEmpty()) { + // Get the name of the function. + const int argc = 1; + v8::Handle<v8::Value> argv[argc] = { exec_state }; + v8::Handle<v8::Value> result = frame_function_name->Call(exec_state, + argc, argv); + if (result->IsUndefined()) { + last_function_hit[0] = '\0'; + } else { + CHECK(result->IsString()); + v8::Handle<v8::String> function_name(result->ToString()); + function_name->WriteAscii(last_function_hit); + } + } + } +} + + +// Debug event handler which counts a number of events and collects the stack +// height if there is a function compiled for that. +int exception_hit_count = 0; +int uncaught_exception_hit_count = 0; +int last_js_stack_height = -1; + +static void DebugEventCounterClear() { + break_point_hit_count = 0; + exception_hit_count = 0; + uncaught_exception_hit_count = 0; +} + +static void DebugEventCounter(v8::DebugEvent event, + v8::Handle<v8::Object> exec_state, + v8::Handle<v8::Object> event_data, + v8::Handle<v8::Value> data) { + // When hitting a debug event listener there must be a break set. + CHECK_NE(v8::internal::Debug::break_id(), 0); + + // Count the number of breaks. + if (event == v8::Break) { + break_point_hit_count++; + } else if (event == v8::Exception) { + exception_hit_count++; + + // Check whether the exception was uncaught. + v8::Local<v8::String> fun_name = v8::String::New("uncaught"); + v8::Local<v8::Function> fun = + v8::Function::Cast(*event_data->Get(fun_name)); + v8::Local<v8::Value> result = *fun->Call(event_data, 0, NULL); + if (result->IsTrue()) { + uncaught_exception_hit_count++; + } + } + + // Collect the JavsScript stack height if the function frame_count is + // compiled. + if (!frame_count.IsEmpty()) { + static const int kArgc = 1; + v8::Handle<v8::Value> argv[kArgc] = { exec_state }; + // Using exec_state as receiver is just to have a receiver. + v8::Handle<v8::Value> result = frame_count->Call(exec_state, kArgc, argv); + last_js_stack_height = result->Int32Value(); + } +} + + +// Debug event handler which evaluates a number of expressions when a break +// point is hit. Each evaluated expression is compared with an expected value. +// For this debug event handler to work the following two global varaibles +// must be initialized. +// checks: An array of expressions and expected results +// evaluate_check_function: A JavaScript function (see below) + +// Structure for holding checks to do. +struct EvaluateCheck { + const char* expr; // An expression to evaluate when a break point is hit. + v8::Handle<v8::Value> expected; // The expected result. +}; +// Array of checks to do. +struct EvaluateCheck* checks = NULL; +// Source for The JavaScript function which can do the evaluation when a break +// point is hit. +const char* evaluate_check_source = + "function evaluate_check(exec_state, expr, expected) {" + " return exec_state.frame(0).evaluate(expr).value() === expected;" + "}"; +v8::Local<v8::Function> evaluate_check_function; + +// The actual debug event described by the longer comment above. +static void DebugEventEvaluate(v8::DebugEvent event, + v8::Handle<v8::Object> exec_state, + v8::Handle<v8::Object> event_data, + v8::Handle<v8::Value> data) { + // When hitting a debug event listener there must be a break set. + CHECK_NE(v8::internal::Debug::break_id(), 0); + + if (event == v8::Break) { + for (int i = 0; checks[i].expr != NULL; i++) { + const int argc = 3; + v8::Handle<v8::Value> argv[argc] = { exec_state, + v8::String::New(checks[i].expr), + checks[i].expected }; + v8::Handle<v8::Value> result = + evaluate_check_function->Call(exec_state, argc, argv); + if (!result->IsTrue()) { + v8::String::AsciiValue ascii(checks[i].expected->ToString()); + V8_Fatal(__FILE__, __LINE__, "%s != %s", checks[i].expr, *ascii); + } + } + } +} + + +// This debug event listener removes a breakpoint in a function +int debug_event_remove_break_point = 0; +static void DebugEventRemoveBreakPoint(v8::DebugEvent event, + v8::Handle<v8::Object> exec_state, + v8::Handle<v8::Object> event_data, + v8::Handle<v8::Value> data) { + // When hitting a debug event listener there must be a break set. + CHECK_NE(v8::internal::Debug::break_id(), 0); + + if (event == v8::Break) { + break_point_hit_count++; + v8::Handle<v8::Function> fun = v8::Handle<v8::Function>::Cast(data); + ClearBreakPoint(debug_event_remove_break_point); + } +} + + +// Debug event handler which counts break points hit and performs a step +// afterwards. +StepAction step_action = StepIn; // Step action to perform when stepping. +static void DebugEventStep(v8::DebugEvent event, + v8::Handle<v8::Object> exec_state, + v8::Handle<v8::Object> event_data, + v8::Handle<v8::Value> data) { + // When hitting a debug event listener there must be a break set. + CHECK_NE(v8::internal::Debug::break_id(), 0); + + if (event == v8::Break) { + break_point_hit_count++; + PrepareStep(step_action); + } +} + + +// Debug event handler which counts break points hit and performs a step +// afterwards. For each call the expected function is checked. +// For this debug event handler to work the following two global varaibles +// must be initialized. +// expected_step_sequence: An array of the expected function call sequence. +// frame_function_name: A JavaScript function (see below). + +// String containing the expected function call sequence. Note: this only works +// if functions have name length of one. +const char* expected_step_sequence = NULL; + +// The actual debug event described by the longer comment above. +static void DebugEventStepSequence(v8::DebugEvent event, + v8::Handle<v8::Object> exec_state, + v8::Handle<v8::Object> event_data, + v8::Handle<v8::Value> data) { + // When hitting a debug event listener there must be a break set. + CHECK_NE(v8::internal::Debug::break_id(), 0); + + if (event == v8::Break || event == v8::Exception) { + // Check that the current function is the expected. + CHECK(break_point_hit_count < + static_cast<int>(strlen(expected_step_sequence))); + const int argc = 1; + v8::Handle<v8::Value> argv[argc] = { exec_state }; + v8::Handle<v8::Value> result = frame_function_name->Call(exec_state, + argc, argv); + CHECK(result->IsString()); + v8::String::AsciiValue function_name(result->ToString()); + CHECK_EQ(1, strlen(*function_name)); + CHECK_EQ((*function_name)[0], + expected_step_sequence[break_point_hit_count]); + + // Perform step. + break_point_hit_count++; + PrepareStep(step_action); + } +} + + +// Debug event handler which performs a garbage collection. +static void DebugEventBreakPointCollectGarbage( + v8::DebugEvent event, + v8::Handle<v8::Object> exec_state, + v8::Handle<v8::Object> event_data, + v8::Handle<v8::Value> data) { + // When hitting a debug event listener there must be a break set. + CHECK_NE(v8::internal::Debug::break_id(), 0); + + // Perform a garbage collection when break point is hit and continue. Based + // on the number of break points hit either scavenge or mark compact + // collector is used. + if (event == v8::Break) { + break_point_hit_count++; + if (break_point_hit_count % 2 == 0) { + // Scavenge. + Heap::CollectGarbage(0, v8::internal::NEW_SPACE); + } else { + // Mark sweep (and perhaps compact). + Heap::CollectAllGarbage(); + } + } +} + + +// Debug event handler which re-issues a debug break and calls the garbage +// collector to have the heap verified. +static void DebugEventBreak(v8::DebugEvent event, + v8::Handle<v8::Object> exec_state, + v8::Handle<v8::Object> event_data, + v8::Handle<v8::Value> data) { + // When hitting a debug event listener there must be a break set. + CHECK_NE(v8::internal::Debug::break_id(), 0); + + if (event == v8::Break) { + // Count the number of breaks. + break_point_hit_count++; + + // Run the garbage collector to enforce heap verification if option + // --verify-heap is set. + Heap::CollectGarbage(0, v8::internal::NEW_SPACE); + + // Set the break flag again to come back here as soon as possible. + v8::Debug::DebugBreak(); + } +} + + +// --- M e s s a g e C a l l b a c k + + +// Message callback which counts the number of messages. +int message_callback_count = 0; + +static void MessageCallbackCountClear() { + message_callback_count = 0; +} + +static void MessageCallbackCount(v8::Handle<v8::Message> message, + v8::Handle<v8::Value> data) { + message_callback_count++; +} + + +// --- T h e A c t u a l T e s t s + + +// Test that the debug break function is the expected one for different kinds +// of break locations. +TEST(DebugStub) { + using ::v8::internal::Builtins; + v8::HandleScope scope; + DebugLocalContext env; + + CheckDebugBreakFunction(&env, + "function f1(){}", "f1", + 0, + v8::internal::RelocInfo::JS_RETURN, + NULL); + CheckDebugBreakFunction(&env, + "function f2(){x=1;}", "f2", + 0, + v8::internal::RelocInfo::CODE_TARGET, + Builtins::builtin(Builtins::StoreIC_DebugBreak)); + CheckDebugBreakFunction(&env, + "function f3(){var a=x;}", "f3", + 0, + v8::internal::RelocInfo::CODE_TARGET_CONTEXT, + Builtins::builtin(Builtins::LoadIC_DebugBreak)); + +// TODO(1240753): Make the test architecture independent or split +// parts of the debugger into architecture dependent files. This +// part currently disabled as it is not portable between IA32/ARM. +// Currently on ICs for keyed store/load on ARM. +#if !defined (__arm__) && !defined(__thumb__) + CheckDebugBreakFunction( + &env, + "function f4(){var index='propertyName'; var a={}; a[index] = 'x';}", + "f4", + 0, + v8::internal::RelocInfo::CODE_TARGET, + Builtins::builtin(Builtins::KeyedStoreIC_DebugBreak)); + CheckDebugBreakFunction( + &env, + "function f5(){var index='propertyName'; var a={}; return a[index];}", + "f5", + 0, + v8::internal::RelocInfo::CODE_TARGET, + Builtins::builtin(Builtins::KeyedLoadIC_DebugBreak)); +#endif + + // Check the debug break code stubs for call ICs with different number of + // parameters. + Handle<Code> debug_break_0 = v8::internal::ComputeCallDebugBreak(0); + Handle<Code> debug_break_1 = v8::internal::ComputeCallDebugBreak(1); + Handle<Code> debug_break_4 = v8::internal::ComputeCallDebugBreak(4); + + CheckDebugBreakFunction(&env, + "function f4_0(){x();}", "f4_0", + 0, + v8::internal::RelocInfo::CODE_TARGET_CONTEXT, + *debug_break_0); + + CheckDebugBreakFunction(&env, + "function f4_1(){x(1);}", "f4_1", + 0, + v8::internal::RelocInfo::CODE_TARGET_CONTEXT, + *debug_break_1); + + CheckDebugBreakFunction(&env, + "function f4_4(){x(1,2,3,4);}", "f4_4", + 0, + v8::internal::RelocInfo::CODE_TARGET_CONTEXT, + *debug_break_4); +} + + +// Test that the debug info in the VM is in sync with the functions being +// debugged. +TEST(DebugInfo) { + v8::HandleScope scope; + DebugLocalContext env; + // Create a couple of functions for the test. + v8::Local<v8::Function> foo = + CompileFunction(&env, "function foo(){}", "foo"); + v8::Local<v8::Function> bar = + CompileFunction(&env, "function bar(){}", "bar"); + // Initially no functions are debugged. + CHECK_EQ(0, v8::internal::GetDebuggedFunctions()->length()); + CHECK(!HasDebugInfo(foo)); + CHECK(!HasDebugInfo(bar)); + // One function (foo) is debugged. + int bp1 = SetBreakPoint(foo, 0); + CHECK_EQ(1, v8::internal::GetDebuggedFunctions()->length()); + CHECK(HasDebugInfo(foo)); + CHECK(!HasDebugInfo(bar)); + // Two functions are debugged. + int bp2 = SetBreakPoint(bar, 0); + CHECK_EQ(2, v8::internal::GetDebuggedFunctions()->length()); + CHECK(HasDebugInfo(foo)); + CHECK(HasDebugInfo(bar)); + // One function (bar) is debugged. + ClearBreakPoint(bp1); + CHECK_EQ(1, v8::internal::GetDebuggedFunctions()->length()); + CHECK(!HasDebugInfo(foo)); + CHECK(HasDebugInfo(bar)); + // No functions are debugged. + ClearBreakPoint(bp2); + CHECK_EQ(0, v8::internal::GetDebuggedFunctions()->length()); + CHECK(!HasDebugInfo(foo)); + CHECK(!HasDebugInfo(bar)); +} + + +// Test that a break point can be set at an IC store location. +TEST(BreakPointICStore) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + v8::Script::Compile(v8::String::New("function foo(){bar=0;}"))->Run(); + v8::Local<v8::Function> foo = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("foo"))); + + // Run without breakpoints. + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Run with breakpoint + int bp = SetBreakPoint(foo, 0); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Run without breakpoints. + ClearBreakPoint(bp); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test that a break point can be set at an IC load location. +TEST(BreakPointICLoad) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + v8::Script::Compile(v8::String::New("bar=1"))->Run(); + v8::Script::Compile(v8::String::New("function foo(){var x=bar;}"))->Run(); + v8::Local<v8::Function> foo = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("foo"))); + + // Run without breakpoints. + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Run with breakpoint + int bp = SetBreakPoint(foo, 0); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Run without breakpoints. + ClearBreakPoint(bp); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test that a break point can be set at an IC call location. +TEST(BreakPointICCall) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + v8::Script::Compile(v8::String::New("function bar(){}"))->Run(); + v8::Script::Compile(v8::String::New("function foo(){bar();}"))->Run(); + v8::Local<v8::Function> foo = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("foo"))); + + // Run without breakpoints. + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Run with breakpoint + int bp = SetBreakPoint(foo, 0); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Run without breakpoints. + ClearBreakPoint(bp); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test that a break point can be set at a return store location. +TEST(BreakPointReturn) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + v8::Script::Compile(v8::String::New("function foo(){}"))->Run(); + v8::Local<v8::Function> foo = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("foo"))); + + // Run without breakpoints. + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Run with breakpoint + int bp = SetBreakPoint(foo, 0); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Run without breakpoints. + ClearBreakPoint(bp); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +static void CallWithBreakPoints(v8::Local<v8::Object> recv, + v8::Local<v8::Function> f, + int break_point_count, + int call_count) { + break_point_hit_count = 0; + for (int i = 0; i < call_count; i++) { + f->Call(recv, 0, NULL); + CHECK_EQ((i + 1) * break_point_count, break_point_hit_count); + } +} + +// Test GC during break point processing. +TEST(GCDuringBreakPointProcessing) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + + v8::Debug::SetDebugEventListener(DebugEventBreakPointCollectGarbage, + v8::Undefined()); + v8::Local<v8::Function> foo; + + // Test IC store break point with garbage collection. + foo = CompileFunction(&env, "function foo(){bar=0;}", "foo"); + SetBreakPoint(foo, 0); + CallWithBreakPoints(env->Global(), foo, 1, 10); + + // Test IC load break point with garbage collection. + foo = CompileFunction(&env, "bar=1;function foo(){var x=bar;}", "foo"); + SetBreakPoint(foo, 0); + CallWithBreakPoints(env->Global(), foo, 1, 10); + + // Test IC call break point with garbage collection. + foo = CompileFunction(&env, "function bar(){};function foo(){bar();}", "foo"); + SetBreakPoint(foo, 0); + CallWithBreakPoints(env->Global(), foo, 1, 10); + + // Test return break point with garbage collection. + foo = CompileFunction(&env, "function foo(){}", "foo"); + SetBreakPoint(foo, 0); + CallWithBreakPoints(env->Global(), foo, 1, 25); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Call the function three times with different garbage collections in between +// and make sure that the break point survives. +static void CallAndGC(v8::Local<v8::Object> recv, v8::Local<v8::Function> f) { + break_point_hit_count = 0; + + for (int i = 0; i < 3; i++) { + // Call function. + f->Call(recv, 0, NULL); + CHECK_EQ(1 + i * 3, break_point_hit_count); + + // Scavenge and call function. + Heap::CollectGarbage(0, v8::internal::NEW_SPACE); + f->Call(recv, 0, NULL); + CHECK_EQ(2 + i * 3, break_point_hit_count); + + // Mark sweep (and perhaps compact) and call function. + Heap::CollectAllGarbage(); + f->Call(recv, 0, NULL); + CHECK_EQ(3 + i * 3, break_point_hit_count); + } +} + + +// Test that a break point can be set at a return store location. +TEST(BreakPointSurviveGC) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + v8::Local<v8::Function> foo; + + // Test IC store break point with garbage collection. + foo = CompileFunction(&env, "function foo(){bar=0;}", "foo"); + SetBreakPoint(foo, 0); + CallAndGC(env->Global(), foo); + + // Test IC load break point with garbage collection. + foo = CompileFunction(&env, "bar=1;function foo(){var x=bar;}", "foo"); + SetBreakPoint(foo, 0); + CallAndGC(env->Global(), foo); + + // Test IC call break point with garbage collection. + foo = CompileFunction(&env, "function bar(){};function foo(){bar();}", "foo"); + SetBreakPoint(foo, 0); + CallAndGC(env->Global(), foo); + + // Test return break point with garbage collection. + foo = CompileFunction(&env, "function foo(){}", "foo"); + SetBreakPoint(foo, 0); + CallAndGC(env->Global(), foo); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test that break points can be set using the global Debug object. +TEST(BreakPointThroughJavaScript) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + v8::Script::Compile(v8::String::New("function bar(){}"))->Run(); + v8::Script::Compile(v8::String::New("function foo(){bar();bar();}"))->Run(); + // 012345678901234567890 + // 1 2 + // Break points are set at position 3 and 9 + v8::Local<v8::Script> foo = v8::Script::Compile(v8::String::New("foo()")); + + // Run without breakpoints. + foo->Run(); + CHECK_EQ(0, break_point_hit_count); + + // Run with one breakpoint + int bp1 = SetBreakPointFromJS("foo", 0, 3); + foo->Run(); + CHECK_EQ(1, break_point_hit_count); + foo->Run(); + CHECK_EQ(2, break_point_hit_count); + + // Run with two breakpoints + int bp2 = SetBreakPointFromJS("foo", 0, 9); + foo->Run(); + CHECK_EQ(4, break_point_hit_count); + foo->Run(); + CHECK_EQ(6, break_point_hit_count); + + // Run with one breakpoint + ClearBreakPointFromJS(bp2); + foo->Run(); + CHECK_EQ(7, break_point_hit_count); + foo->Run(); + CHECK_EQ(8, break_point_hit_count); + + // Run without breakpoints. + ClearBreakPointFromJS(bp1); + foo->Run(); + CHECK_EQ(8, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); + + // Make sure that the break point numbers are consecutive. + CHECK_EQ(1, bp1); + CHECK_EQ(2, bp2); +} + + +// Test that break points on scripts identified by name can be set using the +// global Debug object. +TEST(ScriptBreakPointByNameThroughJavaScript) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + + v8::Local<v8::String> script = v8::String::New( + "function f() {\n" + " function h() {\n" + " a = 0; // line 2\n" + " }\n" + " b = 1; // line 4\n" + " return h();\n" + "}\n" + "\n" + "function g() {\n" + " function h() {\n" + " a = 0;\n" + " }\n" + " b = 2; // line 12\n" + " h();\n" + " b = 3; // line 14\n" + " f(); // line 15\n" + "}"); + + // Compile the script and get the two functions. + v8::ScriptOrigin origin = + v8::ScriptOrigin(v8::String::New("test")); + v8::Script::Compile(script, &origin)->Run(); + v8::Local<v8::Function> f = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + v8::Local<v8::Function> g = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("g"))); + + // Call f and g without break points. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Call f and g with break point on line 12. + int sbp1 = SetScriptBreakPointByNameFromJS("test", 12, 0); + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + // Remove the break point again. + break_point_hit_count = 0; + ClearBreakPointFromJS(sbp1); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Call f and g with break point on line 2. + int sbp2 = SetScriptBreakPointByNameFromJS("test", 2, 0); + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Call f and g with break point on line 2, 4, 12, 14 and 15. + int sbp3 = SetScriptBreakPointByNameFromJS("test", 4, 0); + int sbp4 = SetScriptBreakPointByNameFromJS("test", 12, 0); + int sbp5 = SetScriptBreakPointByNameFromJS("test", 14, 0); + int sbp6 = SetScriptBreakPointByNameFromJS("test", 15, 0); + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(7, break_point_hit_count); + + // Remove all the break points again. + break_point_hit_count = 0; + ClearBreakPointFromJS(sbp2); + ClearBreakPointFromJS(sbp3); + ClearBreakPointFromJS(sbp4); + ClearBreakPointFromJS(sbp5); + ClearBreakPointFromJS(sbp6); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); + + // Make sure that the break point numbers are consecutive. + CHECK_EQ(1, sbp1); + CHECK_EQ(2, sbp2); + CHECK_EQ(3, sbp3); + CHECK_EQ(4, sbp4); + CHECK_EQ(5, sbp5); + CHECK_EQ(6, sbp6); +} + + +TEST(ScriptBreakPointByIdThroughJavaScript) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + + v8::Local<v8::String> source = v8::String::New( + "function f() {\n" + " function h() {\n" + " a = 0; // line 2\n" + " }\n" + " b = 1; // line 4\n" + " return h();\n" + "}\n" + "\n" + "function g() {\n" + " function h() {\n" + " a = 0;\n" + " }\n" + " b = 2; // line 12\n" + " h();\n" + " b = 3; // line 14\n" + " f(); // line 15\n" + "}"); + + // Compile the script and get the two functions. + v8::ScriptOrigin origin = + v8::ScriptOrigin(v8::String::New("test")); + v8::Local<v8::Script> script = v8::Script::Compile(source, &origin); + script->Run(); + v8::Local<v8::Function> f = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + v8::Local<v8::Function> g = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("g"))); + + // Get the script id knowing that internally it is a 32 integer. + uint32_t script_id = script->Id()->Uint32Value(); + + // Call f and g without break points. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Call f and g with break point on line 12. + int sbp1 = SetScriptBreakPointByIdFromJS(script_id, 12, 0); + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + // Remove the break point again. + break_point_hit_count = 0; + ClearBreakPointFromJS(sbp1); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Call f and g with break point on line 2. + int sbp2 = SetScriptBreakPointByIdFromJS(script_id, 2, 0); + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Call f and g with break point on line 2, 4, 12, 14 and 15. + int sbp3 = SetScriptBreakPointByIdFromJS(script_id, 4, 0); + int sbp4 = SetScriptBreakPointByIdFromJS(script_id, 12, 0); + int sbp5 = SetScriptBreakPointByIdFromJS(script_id, 14, 0); + int sbp6 = SetScriptBreakPointByIdFromJS(script_id, 15, 0); + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(7, break_point_hit_count); + + // Remove all the break points again. + break_point_hit_count = 0; + ClearBreakPointFromJS(sbp2); + ClearBreakPointFromJS(sbp3); + ClearBreakPointFromJS(sbp4); + ClearBreakPointFromJS(sbp5); + ClearBreakPointFromJS(sbp6); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); + + // Make sure that the break point numbers are consecutive. + CHECK_EQ(1, sbp1); + CHECK_EQ(2, sbp2); + CHECK_EQ(3, sbp3); + CHECK_EQ(4, sbp4); + CHECK_EQ(5, sbp5); + CHECK_EQ(6, sbp6); +} + + +// Test conditional script break points. +TEST(EnableDisableScriptBreakPoint) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + + v8::Local<v8::String> script = v8::String::New( + "function f() {\n" + " a = 0; // line 1\n" + "};"); + + // Compile the script and get function f. + v8::ScriptOrigin origin = + v8::ScriptOrigin(v8::String::New("test")); + v8::Script::Compile(script, &origin)->Run(); + v8::Local<v8::Function> f = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + + // Set script break point on line 1 (in function f). + int sbp = SetScriptBreakPointByNameFromJS("test", 1, 0); + + // Call f while enabeling and disabling the script break point. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + DisableScriptBreakPointFromJS(sbp); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + EnableScriptBreakPointFromJS(sbp); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + DisableScriptBreakPointFromJS(sbp); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Reload the script and get f again checking that the disabeling survives. + v8::Script::Compile(script, &origin)->Run(); + f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + EnableScriptBreakPointFromJS(sbp); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(3, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test conditional script break points. +TEST(ConditionalScriptBreakPoint) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + + v8::Local<v8::String> script = v8::String::New( + "count = 0;\n" + "function f() {\n" + " g(count++); // line 2\n" + "};\n" + "function g(x) {\n" + " var a=x; // line 5\n" + "};"); + + // Compile the script and get function f. + v8::ScriptOrigin origin = + v8::ScriptOrigin(v8::String::New("test")); + v8::Script::Compile(script, &origin)->Run(); + v8::Local<v8::Function> f = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + + // Set script break point on line 5 (in function g). + int sbp1 = SetScriptBreakPointByNameFromJS("test", 5, 0); + + // Call f with different conditions on the script break point. + break_point_hit_count = 0; + ChangeScriptBreakPointConditionFromJS(sbp1, "false"); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + ChangeScriptBreakPointConditionFromJS(sbp1, "true"); + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + ChangeScriptBreakPointConditionFromJS(sbp1, "a % 2 == 0"); + break_point_hit_count = 0; + for (int i = 0; i < 10; i++) { + f->Call(env->Global(), 0, NULL); + } + CHECK_EQ(5, break_point_hit_count); + + // Reload the script and get f again checking that the condition survives. + v8::Script::Compile(script, &origin)->Run(); + f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + + break_point_hit_count = 0; + for (int i = 0; i < 10; i++) { + f->Call(env->Global(), 0, NULL); + } + CHECK_EQ(5, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test ignore count on script break points. +TEST(ScriptBreakPointIgnoreCount) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + + v8::Local<v8::String> script = v8::String::New( + "function f() {\n" + " a = 0; // line 1\n" + "};"); + + // Compile the script and get function f. + v8::ScriptOrigin origin = + v8::ScriptOrigin(v8::String::New("test")); + v8::Script::Compile(script, &origin)->Run(); + v8::Local<v8::Function> f = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + + // Set script break point on line 1 (in function f). + int sbp = SetScriptBreakPointByNameFromJS("test", 1, 0); + + // Call f with different ignores on the script break point. + break_point_hit_count = 0; + ChangeScriptBreakPointIgnoreCountFromJS(sbp, 1); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + ChangeScriptBreakPointIgnoreCountFromJS(sbp, 5); + break_point_hit_count = 0; + for (int i = 0; i < 10; i++) { + f->Call(env->Global(), 0, NULL); + } + CHECK_EQ(5, break_point_hit_count); + + // Reload the script and get f again checking that the ignore survives. + v8::Script::Compile(script, &origin)->Run(); + f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + + break_point_hit_count = 0; + for (int i = 0; i < 10; i++) { + f->Call(env->Global(), 0, NULL); + } + CHECK_EQ(5, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test that script break points survive when a script is reloaded. +TEST(ScriptBreakPointReload) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + + v8::Local<v8::Function> f; + v8::Local<v8::String> script = v8::String::New( + "function f() {\n" + " function h() {\n" + " a = 0; // line 2\n" + " }\n" + " b = 1; // line 4\n" + " return h();\n" + "}"); + + v8::ScriptOrigin origin_1 = v8::ScriptOrigin(v8::String::New("1")); + v8::ScriptOrigin origin_2 = v8::ScriptOrigin(v8::String::New("2")); + + // Set a script break point before the script is loaded. + SetScriptBreakPointByNameFromJS("1", 2, 0); + + // Compile the script and get the function. + v8::Script::Compile(script, &origin_1)->Run(); + f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + + // Call f and check that the script break point is active. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + // Compile the script again with a different script data and get the + // function. + v8::Script::Compile(script, &origin_2)->Run(); + f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + + // Call f and check that no break points are set. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Compile the script again and get the function. + v8::Script::Compile(script, &origin_1)->Run(); + f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + + // Call f and check that the script break point is active. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test when several scripts has the same script data +TEST(ScriptBreakPointMultiple) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + + v8::Local<v8::Function> f; + v8::Local<v8::String> script_f = v8::String::New( + "function f() {\n" + " a = 0; // line 1\n" + "}"); + + v8::Local<v8::Function> g; + v8::Local<v8::String> script_g = v8::String::New( + "function g() {\n" + " b = 0; // line 1\n" + "}"); + + v8::ScriptOrigin origin = + v8::ScriptOrigin(v8::String::New("test")); + + // Set a script break point before the scripts are loaded. + int sbp = SetScriptBreakPointByNameFromJS("test", 1, 0); + + // Compile the scripts with same script data and get the functions. + v8::Script::Compile(script_f, &origin)->Run(); + f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + v8::Script::Compile(script_g, &origin)->Run(); + g = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("g"))); + + // Call f and g and check that the script break point is active. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Clear the script break point. + ClearBreakPointFromJS(sbp); + + // Call f and g and check that the script break point is no longer active. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Set script break point with the scripts loaded. + sbp = SetScriptBreakPointByNameFromJS("test", 1, 0); + + // Call f and g and check that the script break point is active. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test the script origin which has both name and line offset. +TEST(ScriptBreakPointLineOffset) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + + v8::Local<v8::Function> f; + v8::Local<v8::String> script = v8::String::New( + "function f() {\n" + " a = 0; // line 8 as this script has line offset 7\n" + " b = 0; // line 9 as this script has line offset 7\n" + "}"); + + // Create script origin both name and line offset. + v8::ScriptOrigin origin(v8::String::New("test.html"), + v8::Integer::New(7)); + + // Set two script break points before the script is loaded. + int sbp1 = SetScriptBreakPointByNameFromJS("test.html", 8, 0); + int sbp2 = SetScriptBreakPointByNameFromJS("test.html", 9, 0); + + // Compile the script and get the function. + v8::Script::Compile(script, &origin)->Run(); + f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + + // Call f and check that the script break point is active. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Clear the script break points. + ClearBreakPointFromJS(sbp1); + ClearBreakPointFromJS(sbp2); + + // Call f and check that no script break points are active. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Set a script break point with the script loaded. + sbp1 = SetScriptBreakPointByNameFromJS("test.html", 9, 0); + + // Call f and check that the script break point is active. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test script break points set on lines. +TEST(ScriptBreakPointLine) { + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + // Create a function for checking the function when hitting a break point. + frame_function_name = CompileFunction(&env, + frame_function_name_source, + "frame_function_name"); + + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + + v8::Local<v8::Function> f; + v8::Local<v8::Function> g; + v8::Local<v8::String> script = v8::String::New( + "a = 0 // line 0\n" + "function f() {\n" + " a = 1; // line 2\n" + "}\n" + " a = 2; // line 4\n" + " /* xx */ function g() { // line 5\n" + " function h() { // line 6\n" + " a = 3; // line 7\n" + " }\n" + " h(); // line 9\n" + " a = 4; // line 10\n" + " }\n" + " a=5; // line 12"); + + // Set a couple script break point before the script is loaded. + int sbp1 = SetScriptBreakPointByNameFromJS("test.html", 0, -1); + int sbp2 = SetScriptBreakPointByNameFromJS("test.html", 1, -1); + int sbp3 = SetScriptBreakPointByNameFromJS("test.html", 5, -1); + + // Compile the script and get the function. + break_point_hit_count = 0; + v8::ScriptOrigin origin(v8::String::New("test.html"), v8::Integer::New(0)); + v8::Script::Compile(script, &origin)->Run(); + f = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("f"))); + g = v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("g"))); + + // Chesk that a break point was hit when the script was run. + CHECK_EQ(1, break_point_hit_count); + CHECK_EQ(0, strlen(last_function_hit)); + + // Call f and check that the script break point. + f->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + CHECK_EQ("f", last_function_hit); + + // Call g and check that the script break point. + g->Call(env->Global(), 0, NULL); + CHECK_EQ(3, break_point_hit_count); + CHECK_EQ("g", last_function_hit); + + // Clear the script break point on g and set one on h. + ClearBreakPointFromJS(sbp3); + int sbp4 = SetScriptBreakPointByNameFromJS("test.html", 6, -1); + + // Call g and check that the script break point in h is hit. + g->Call(env->Global(), 0, NULL); + CHECK_EQ(4, break_point_hit_count); + CHECK_EQ("h", last_function_hit); + + // Clear break points in f and h. Set a new one in the script between + // functions f and g and test that there is no break points in f and g any + // more. + ClearBreakPointFromJS(sbp2); + ClearBreakPointFromJS(sbp4); + int sbp5 = SetScriptBreakPointByNameFromJS("test.html", 4, -1); + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + g->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Reload the script which should hit two break points. + break_point_hit_count = 0; + v8::Script::Compile(script, &origin)->Run(); + CHECK_EQ(2, break_point_hit_count); + CHECK_EQ(0, strlen(last_function_hit)); + + // Set a break point in the code after the last function decleration. + int sbp6 = SetScriptBreakPointByNameFromJS("test.html", 12, -1); + + // Reload the script which should hit three break points. + break_point_hit_count = 0; + v8::Script::Compile(script, &origin)->Run(); + CHECK_EQ(3, break_point_hit_count); + CHECK_EQ(0, strlen(last_function_hit)); + + // Clear the last break points, and reload the script which should not hit any + // break points. + ClearBreakPointFromJS(sbp1); + ClearBreakPointFromJS(sbp5); + ClearBreakPointFromJS(sbp6); + break_point_hit_count = 0; + v8::Script::Compile(script, &origin)->Run(); + CHECK_EQ(0, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test that it is possible to remove the last break point for a function +// inside the break handling of that break point. +TEST(RemoveBreakPointInBreak) { + v8::HandleScope scope; + DebugLocalContext env; + + v8::Local<v8::Function> foo = + CompileFunction(&env, "function foo(){a=1;}", "foo"); + debug_event_remove_break_point = SetBreakPoint(foo, 0); + + // Register the debug event listener pasing the function + v8::Debug::SetDebugEventListener(DebugEventRemoveBreakPoint, foo); + + break_point_hit_count = 0; + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + break_point_hit_count = 0; + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test that the debugger statement causes a break. +TEST(DebuggerStatement) { + break_point_hit_count = 0; + v8::HandleScope scope; + DebugLocalContext env; + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + v8::Script::Compile(v8::String::New("function bar(){debugger}"))->Run(); + v8::Script::Compile(v8::String::New( + "function foo(){debugger;debugger;}"))->Run(); + v8::Local<v8::Function> foo = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("foo"))); + v8::Local<v8::Function> bar = + v8::Local<v8::Function>::Cast(env->Global()->Get(v8::String::New("bar"))); + + // Run function with debugger statement + bar->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + // Run function with two debugger statement + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(3, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Thest that the evaluation of expressions when a break point is hit generates +// the correct results. +TEST(DebugEvaluate) { + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + // Create a function for checking the evaluation when hitting a break point. + evaluate_check_function = CompileFunction(&env, + evaluate_check_source, + "evaluate_check"); + // Register the debug event listener + v8::Debug::SetDebugEventListener(DebugEventEvaluate); + + // Different expected vaules of x and a when in a break point (u = undefined, + // d = Hello, world!). + struct EvaluateCheck checks_uu[] = { + {"x", v8::Undefined()}, + {"a", v8::Undefined()}, + {NULL, v8::Handle<v8::Value>()} + }; + struct EvaluateCheck checks_hu[] = { + {"x", v8::String::New("Hello, world!")}, + {"a", v8::Undefined()}, + {NULL, v8::Handle<v8::Value>()} + }; + struct EvaluateCheck checks_hh[] = { + {"x", v8::String::New("Hello, world!")}, + {"a", v8::String::New("Hello, world!")}, + {NULL, v8::Handle<v8::Value>()} + }; + + // Simple test function. The "y=0" is in the function foo to provide a break + // location. For "y=0" the "y" is at position 15 in the barbar function + // therefore setting breakpoint at position 15 will break at "y=0" and + // setting it higher will break after. + v8::Local<v8::Function> foo = CompileFunction(&env, + "function foo(x) {" + " var a;" + " y=0; /* To ensure break location.*/" + " a=x;" + "}", + "foo"); + const int foo_break_position = 15; + + // Arguments with one parameter "Hello, world!" + v8::Handle<v8::Value> argv_foo[1] = { v8::String::New("Hello, world!") }; + + // Call foo with breakpoint set before a=x and undefined as parameter. + int bp = SetBreakPoint(foo, foo_break_position); + checks = checks_uu; + foo->Call(env->Global(), 0, NULL); + + // Call foo with breakpoint set before a=x and parameter "Hello, world!". + checks = checks_hu; + foo->Call(env->Global(), 1, argv_foo); + + // Call foo with breakpoint set after a=x and parameter "Hello, world!". + ClearBreakPoint(bp); + SetBreakPoint(foo, foo_break_position + 1); + checks = checks_hh; + foo->Call(env->Global(), 1, argv_foo); + + // Test function with an inner function. The "y=0" is in function barbar + // to provide a break location. For "y=0" the "y" is at position 8 in the + // barbar function therefore setting breakpoint at position 8 will break at + // "y=0" and setting it higher will break after. + v8::Local<v8::Function> bar = CompileFunction(&env, + "y = 0;" + "x = 'Goodbye, world!';" + "function bar(x, b) {" + " var a;" + " function barbar() {" + " y=0; /* To ensure break location.*/" + " a=x;" + " };" + " debug.Debug.clearAllBreakPoints();" + " barbar();" + " y=0;a=x;" + "}", + "bar"); + const int barbar_break_position = 8; + + // Call bar setting breakpoint before a=x in barbar and undefined as + // parameter. + checks = checks_uu; + v8::Handle<v8::Value> argv_bar_1[2] = { + v8::Undefined(), + v8::Number::New(barbar_break_position) + }; + bar->Call(env->Global(), 2, argv_bar_1); + + // Call bar setting breakpoint before a=x in barbar and parameter + // "Hello, world!". + checks = checks_hu; + v8::Handle<v8::Value> argv_bar_2[2] = { + v8::String::New("Hello, world!"), + v8::Number::New(barbar_break_position) + }; + bar->Call(env->Global(), 2, argv_bar_2); + + // Call bar setting breakpoint after a=x in barbar and parameter + // "Hello, world!". + checks = checks_hh; + v8::Handle<v8::Value> argv_bar_3[2] = { + v8::String::New("Hello, world!"), + v8::Number::New(barbar_break_position + 1) + }; + bar->Call(env->Global(), 2, argv_bar_3); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Simple test of the stepping mechanism using only store ICs. +TEST(DebugStepLinear) { + v8::HandleScope scope; + DebugLocalContext env; + + // Create a function for testing stepping. + v8::Local<v8::Function> foo = CompileFunction(&env, + "function foo(){a=1;b=1;c=1;}", + "foo"); + SetBreakPoint(foo, 3); + + // Register a debug event listener which steps and counts. + v8::Debug::SetDebugEventListener(DebugEventStep); + + step_action = StepIn; + break_point_hit_count = 0; + foo->Call(env->Global(), 0, NULL); + + // With stepping all break locations are hit. + CHECK_EQ(4, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); + + // Register a debug event listener which just counts. + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount); + + SetBreakPoint(foo, 3); + break_point_hit_count = 0; + foo->Call(env->Global(), 0, NULL); + + // Without stepping only active break points are hit. + CHECK_EQ(1, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test the stepping mechanism with different ICs. +TEST(DebugStepLinearMixedICs) { + v8::HandleScope scope; + DebugLocalContext env; + + // Create a function for testing stepping. + v8::Local<v8::Function> foo = CompileFunction(&env, + "function bar() {};" + "function foo() {" + " var x;" + " var index='name';" + " var y = {};" + " a=1;b=2;x=a;y[index]=3;x=y[index];bar();}", "foo"); + SetBreakPoint(foo, 0); + + // Register a debug event listener which steps and counts. + v8::Debug::SetDebugEventListener(DebugEventStep); + + step_action = StepIn; + break_point_hit_count = 0; + foo->Call(env->Global(), 0, NULL); + + // With stepping all break locations are hit. For ARM the keyed load/store + // is not hit as they are not implemented as ICs. +#if defined (__arm__) || defined(__thumb__) + CHECK_EQ(6, break_point_hit_count); +#else + CHECK_EQ(8, break_point_hit_count); +#endif + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); + + // Register a debug event listener which just counts. + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount); + + SetBreakPoint(foo, 0); + break_point_hit_count = 0; + foo->Call(env->Global(), 0, NULL); + + // Without stepping only active break points are hit. + CHECK_EQ(1, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +TEST(DebugStepIf) { + v8::HandleScope scope; + DebugLocalContext env; + + // Register a debug event listener which steps and counts. + v8::Debug::SetDebugEventListener(DebugEventStep); + + // Create a function for testing stepping. + const int argc = 1; + const char* src = "function foo(x) { " + " a = 1;" + " if (x) {" + " b = 1;" + " } else {" + " c = 1;" + " d = 1;" + " }" + "}"; + v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo"); + SetBreakPoint(foo, 0); + + // Stepping through the true part. + step_action = StepIn; + break_point_hit_count = 0; + v8::Handle<v8::Value> argv_true[argc] = { v8::True() }; + foo->Call(env->Global(), argc, argv_true); + CHECK_EQ(3, break_point_hit_count); + + // Stepping through the false part. + step_action = StepIn; + break_point_hit_count = 0; + v8::Handle<v8::Value> argv_false[argc] = { v8::False() }; + foo->Call(env->Global(), argc, argv_false); + CHECK_EQ(4, break_point_hit_count); + + // Get rid of the debug event listener. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +TEST(DebugStepSwitch) { + v8::HandleScope scope; + DebugLocalContext env; + + // Register a debug event listener which steps and counts. + v8::Debug::SetDebugEventListener(DebugEventStep); + + // Create a function for testing stepping. + const int argc = 1; + const char* src = "function foo(x) { " + " a = 1;" + " switch (x) {" + " case 1:" + " b = 1;" + " case 2:" + " c = 1;" + " break;" + " case 3:" + " d = 1;" + " e = 1;" + " break;" + " }" + "}"; + v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo"); + SetBreakPoint(foo, 0); + + // One case with fall-through. + step_action = StepIn; + break_point_hit_count = 0; + v8::Handle<v8::Value> argv_1[argc] = { v8::Number::New(1) }; + foo->Call(env->Global(), argc, argv_1); + CHECK_EQ(4, break_point_hit_count); + + // Another case. + step_action = StepIn; + break_point_hit_count = 0; + v8::Handle<v8::Value> argv_2[argc] = { v8::Number::New(2) }; + foo->Call(env->Global(), argc, argv_2); + CHECK_EQ(3, break_point_hit_count); + + // Last case. + step_action = StepIn; + break_point_hit_count = 0; + v8::Handle<v8::Value> argv_3[argc] = { v8::Number::New(3) }; + foo->Call(env->Global(), argc, argv_3); + CHECK_EQ(4, break_point_hit_count); + + // Get rid of the debug event listener. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +TEST(DebugStepFor) { + v8::HandleScope scope; + DebugLocalContext env; + + // Register a debug event listener which steps and counts. + v8::Debug::SetDebugEventListener(DebugEventStep); + + // Create a function for testing stepping. + const int argc = 1; + const char* src = "function foo(x) { " + " a = 1;" + " for (i = 0; i < x; i++) {" + " b = 1;" + " }" + "}"; + v8::Local<v8::Function> foo = CompileFunction(&env, src, "foo"); + SetBreakPoint(foo, 8); // "a = 1;" + + // Looping 10 times. + step_action = StepIn; + break_point_hit_count = 0; + v8::Handle<v8::Value> argv_10[argc] = { v8::Number::New(10) }; + foo->Call(env->Global(), argc, argv_10); + CHECK_EQ(23, break_point_hit_count); + + // Looping 100 times. + step_action = StepIn; + break_point_hit_count = 0; + v8::Handle<v8::Value> argv_100[argc] = { v8::Number::New(100) }; + foo->Call(env->Global(), argc, argv_100); + CHECK_EQ(203, break_point_hit_count); + + // Get rid of the debug event listener. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +TEST(StepInOutSimple) { + v8::HandleScope scope; + DebugLocalContext env; + + // Create a function for checking the function when hitting a break point. + frame_function_name = CompileFunction(&env, + frame_function_name_source, + "frame_function_name"); + + // Register a debug event listener which steps and counts. + v8::Debug::SetDebugEventListener(DebugEventStepSequence); + + // Create functions for testing stepping. + const char* src = "function a() {b();c();}; " + "function b() {c();}; " + "function c() {}; "; + v8::Local<v8::Function> a = CompileFunction(&env, src, "a"); + SetBreakPoint(a, 0); + + // Step through invocation of a with step in. + step_action = StepIn; + break_point_hit_count = 0; + expected_step_sequence = "abcbaca"; + a->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Step through invocation of a with step next. + step_action = StepNext; + break_point_hit_count = 0; + expected_step_sequence = "aaa"; + a->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Step through invocation of a with step out. + step_action = StepOut; + break_point_hit_count = 0; + expected_step_sequence = "a"; + a->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Get rid of the debug event listener. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +TEST(StepInOutTree) { + v8::HandleScope scope; + DebugLocalContext env; + + // Create a function for checking the function when hitting a break point. + frame_function_name = CompileFunction(&env, + frame_function_name_source, + "frame_function_name"); + + // Register a debug event listener which steps and counts. + v8::Debug::SetDebugEventListener(DebugEventStepSequence); + + // Create functions for testing stepping. + const char* src = "function a() {b(c(d()),d());c(d());d()}; " + "function b(x,y) {c();}; " + "function c(x) {}; " + "function d() {}; "; + v8::Local<v8::Function> a = CompileFunction(&env, src, "a"); + SetBreakPoint(a, 0); + + // Step through invocation of a with step in. + step_action = StepIn; + break_point_hit_count = 0; + expected_step_sequence = "adacadabcbadacada"; + a->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Step through invocation of a with step next. + step_action = StepNext; + break_point_hit_count = 0; + expected_step_sequence = "aaaa"; + a->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Step through invocation of a with step out. + step_action = StepOut; + break_point_hit_count = 0; + expected_step_sequence = "a"; + a->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Get rid of the debug event listener. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(true); +} + + +TEST(StepInOutBranch) { + v8::HandleScope scope; + DebugLocalContext env; + + // Create a function for checking the function when hitting a break point. + frame_function_name = CompileFunction(&env, + frame_function_name_source, + "frame_function_name"); + + // Register a debug event listener which steps and counts. + v8::Debug::SetDebugEventListener(DebugEventStepSequence); + + // Create functions for testing stepping. + const char* src = "function a() {b(false);c();}; " + "function b(x) {if(x){c();};}; " + "function c() {}; "; + v8::Local<v8::Function> a = CompileFunction(&env, src, "a"); + SetBreakPoint(a, 0); + + // Step through invocation of a. + step_action = StepIn; + break_point_hit_count = 0; + expected_step_sequence = "abaca"; + a->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Get rid of the debug event listener. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test that step in does not step into native functions. +TEST(DebugStepNatives) { + v8::HandleScope scope; + DebugLocalContext env; + + // Create a function for testing stepping. + v8::Local<v8::Function> foo = CompileFunction( + &env, + "function foo(){debugger;Math.sin(1);}", + "foo"); + + // Register a debug event listener which steps and counts. + v8::Debug::SetDebugEventListener(DebugEventStep); + + step_action = StepIn; + break_point_hit_count = 0; + foo->Call(env->Global(), 0, NULL); + + // With stepping all break locations are hit. + CHECK_EQ(3, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); + + // Register a debug event listener which just counts. + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount); + + break_point_hit_count = 0; + foo->Call(env->Global(), 0, NULL); + + // Without stepping only active break points are hit. + CHECK_EQ(1, break_point_hit_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test break on exceptions. For each exception break combination the number +// of debug event exception callbacks and message callbacks are collected. The +// number of debug event exception callbacks are used to check that the +// debugger is called correctly and the number of message callbacks is used to +// check that uncaught exceptions are still returned even if there is a break +// for them. +TEST(BreakOnException) { + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::internal::Top::TraceException(false); + + // Create functions for testing break on exception. + v8::Local<v8::Function> throws = + CompileFunction(&env, "function throws(){throw 1;}", "throws"); + v8::Local<v8::Function> caught = + CompileFunction(&env, + "function caught(){try {throws();} catch(e) {};}", + "caught"); + v8::Local<v8::Function> notCaught = + CompileFunction(&env, "function notCaught(){throws();}", "notCaught"); + + v8::V8::AddMessageListener(MessageCallbackCount); + v8::Debug::SetDebugEventListener(DebugEventCounter); + + // Initial state should be break on uncaught exception. + DebugEventCounterClear(); + MessageCallbackCountClear(); + caught->Call(env->Global(), 0, NULL); + CHECK_EQ(0, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(0, message_callback_count); + notCaught->Call(env->Global(), 0, NULL); + CHECK_EQ(1, exception_hit_count); + CHECK_EQ(1, uncaught_exception_hit_count); + CHECK_EQ(1, message_callback_count); + + // No break on exception + DebugEventCounterClear(); + MessageCallbackCountClear(); + ChangeBreakOnException(false, false); + caught->Call(env->Global(), 0, NULL); + CHECK_EQ(0, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(0, message_callback_count); + notCaught->Call(env->Global(), 0, NULL); + CHECK_EQ(0, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(1, message_callback_count); + + // Break on uncaught exception + DebugEventCounterClear(); + MessageCallbackCountClear(); + ChangeBreakOnException(false, true); + caught->Call(env->Global(), 0, NULL); + CHECK_EQ(0, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(0, message_callback_count); + notCaught->Call(env->Global(), 0, NULL); + CHECK_EQ(1, exception_hit_count); + CHECK_EQ(1, uncaught_exception_hit_count); + CHECK_EQ(1, message_callback_count); + + // Break on exception and uncaught exception + DebugEventCounterClear(); + MessageCallbackCountClear(); + ChangeBreakOnException(true, true); + caught->Call(env->Global(), 0, NULL); + CHECK_EQ(1, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(0, message_callback_count); + notCaught->Call(env->Global(), 0, NULL); + CHECK_EQ(2, exception_hit_count); + CHECK_EQ(1, uncaught_exception_hit_count); + CHECK_EQ(1, message_callback_count); + + // Break on exception + DebugEventCounterClear(); + MessageCallbackCountClear(); + ChangeBreakOnException(true, false); + caught->Call(env->Global(), 0, NULL); + CHECK_EQ(1, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(0, message_callback_count); + notCaught->Call(env->Global(), 0, NULL); + CHECK_EQ(2, exception_hit_count); + CHECK_EQ(1, uncaught_exception_hit_count); + CHECK_EQ(1, message_callback_count); + + // No break on exception using JavaScript + DebugEventCounterClear(); + MessageCallbackCountClear(); + ChangeBreakOnExceptionFromJS(false, false); + caught->Call(env->Global(), 0, NULL); + CHECK_EQ(0, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(0, message_callback_count); + notCaught->Call(env->Global(), 0, NULL); + CHECK_EQ(0, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(1, message_callback_count); + + // Break on uncaught exception using JavaScript + DebugEventCounterClear(); + MessageCallbackCountClear(); + ChangeBreakOnExceptionFromJS(false, true); + caught->Call(env->Global(), 0, NULL); + CHECK_EQ(0, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(0, message_callback_count); + notCaught->Call(env->Global(), 0, NULL); + CHECK_EQ(1, exception_hit_count); + CHECK_EQ(1, uncaught_exception_hit_count); + CHECK_EQ(1, message_callback_count); + + // Break on exception and uncaught exception using JavaScript + DebugEventCounterClear(); + MessageCallbackCountClear(); + ChangeBreakOnExceptionFromJS(true, true); + caught->Call(env->Global(), 0, NULL); + CHECK_EQ(1, exception_hit_count); + CHECK_EQ(0, message_callback_count); + CHECK_EQ(0, uncaught_exception_hit_count); + notCaught->Call(env->Global(), 0, NULL); + CHECK_EQ(2, exception_hit_count); + CHECK_EQ(1, uncaught_exception_hit_count); + CHECK_EQ(1, message_callback_count); + + // Break on exception using JavaScript + DebugEventCounterClear(); + MessageCallbackCountClear(); + ChangeBreakOnExceptionFromJS(true, false); + caught->Call(env->Global(), 0, NULL); + CHECK_EQ(1, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(0, message_callback_count); + notCaught->Call(env->Global(), 0, NULL); + CHECK_EQ(2, exception_hit_count); + CHECK_EQ(1, uncaught_exception_hit_count); + CHECK_EQ(1, message_callback_count); + + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); + v8::V8::RemoveMessageListeners(MessageCallbackCount); +} + + +// Test break on exception from compiler errors. When compiling using +// v8::Script::Compile there is no JavaScript stack whereas when compiling using +// eval there are JavaScript frames. +TEST(BreakOnCompileException) { + v8::HandleScope scope; + DebugLocalContext env; + + v8::internal::Top::TraceException(false); + + // Create a function for checking the function when hitting a break point. + frame_count = CompileFunction(&env, frame_count_source, "frame_count"); + + v8::V8::AddMessageListener(MessageCallbackCount); + v8::Debug::SetDebugEventListener(DebugEventCounter); + + DebugEventCounterClear(); + MessageCallbackCountClear(); + + // Check initial state. + CHECK_EQ(0, exception_hit_count); + CHECK_EQ(0, uncaught_exception_hit_count); + CHECK_EQ(0, message_callback_count); + CHECK_EQ(-1, last_js_stack_height); + + // Throws SyntaxError: Unexpected end of input + v8::Script::Compile(v8::String::New("+++")); + CHECK_EQ(1, exception_hit_count); + CHECK_EQ(1, uncaught_exception_hit_count); + CHECK_EQ(1, message_callback_count); + CHECK_EQ(0, last_js_stack_height); // No JavaScript stack. + + // Throws SyntaxError: Unexpected identifier + v8::Script::Compile(v8::String::New("x x")); + CHECK_EQ(2, exception_hit_count); + CHECK_EQ(2, uncaught_exception_hit_count); + CHECK_EQ(2, message_callback_count); + CHECK_EQ(0, last_js_stack_height); // No JavaScript stack. + + // Throws SyntaxError: Unexpected end of input + v8::Script::Compile(v8::String::New("eval('+++')"))->Run(); + CHECK_EQ(3, exception_hit_count); + CHECK_EQ(3, uncaught_exception_hit_count); + CHECK_EQ(3, message_callback_count); + CHECK_EQ(1, last_js_stack_height); + + // Throws SyntaxError: Unexpected identifier + v8::Script::Compile(v8::String::New("eval('x x')"))->Run(); + CHECK_EQ(4, exception_hit_count); + CHECK_EQ(4, uncaught_exception_hit_count); + CHECK_EQ(4, message_callback_count); + CHECK_EQ(1, last_js_stack_height); +} + + +TEST(StepWithException) { + v8::HandleScope scope; + DebugLocalContext env; + + // Create a function for checking the function when hitting a break point. + frame_function_name = CompileFunction(&env, + frame_function_name_source, + "frame_function_name"); + + // Register a debug event listener which steps and counts. + v8::Debug::SetDebugEventListener(DebugEventStepSequence); + + // Create functions for testing stepping. + const char* src = "function a() { n(); }; " + "function b() { c(); }; " + "function c() { n(); }; " + "function d() { x = 1; try { e(); } catch(x) { x = 2; } }; " + "function e() { n(); }; " + "function f() { x = 1; try { g(); } catch(x) { x = 2; } }; " + "function g() { h(); }; " + "function h() { x = 1; throw 1; }; "; + + // Step through invocation of a. + v8::Local<v8::Function> a = CompileFunction(&env, src, "a"); + SetBreakPoint(a, 0); + step_action = StepIn; + break_point_hit_count = 0; + expected_step_sequence = "aa"; + a->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Step through invocation of b + c. + v8::Local<v8::Function> b = CompileFunction(&env, src, "b"); + SetBreakPoint(b, 0); + step_action = StepIn; + break_point_hit_count = 0; + expected_step_sequence = "bcc"; + b->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Step through invocation of d + e. + v8::Local<v8::Function> d = CompileFunction(&env, src, "d"); + SetBreakPoint(d, 0); + ChangeBreakOnException(false, true); + step_action = StepIn; + break_point_hit_count = 0; + expected_step_sequence = "dded"; + d->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Step through invocation of d + e now with break on caught exceptions. + ChangeBreakOnException(true, true); + step_action = StepIn; + break_point_hit_count = 0; + expected_step_sequence = "ddeed"; + d->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Step through invocation of f + g + h. + v8::Local<v8::Function> f = CompileFunction(&env, src, "f"); + SetBreakPoint(f, 0); + ChangeBreakOnException(false, true); + step_action = StepIn; + break_point_hit_count = 0; + expected_step_sequence = "ffghf"; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Step through invocation of f + g + h now with break on caught exceptions. + ChangeBreakOnException(true, true); + step_action = StepIn; + break_point_hit_count = 0; + expected_step_sequence = "ffghhf"; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(strlen(expected_step_sequence), break_point_hit_count); + + // Get rid of the debug event listener. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +TEST(DebugBreak) { + v8::HandleScope scope; + DebugLocalContext env; + + // This test should be run with option --verify-heap. This is an ASSERT and + // not a CHECK as --verify-heap is only available in debug mode. + ASSERT(v8::internal::FLAG_verify_heap); + + // Register a debug event listener which sets the break flag and counts. + v8::Debug::SetDebugEventListener(DebugEventBreak); + + // Create a function for testing stepping. + const char* src = "function f0() {}" + "function f1(x1) {}" + "function f2(x1,x2) {}" + "function f3(x1,x2,x3) {}"; + v8::Local<v8::Function> f0 = CompileFunction(&env, src, "f0"); + v8::Local<v8::Function> f1 = CompileFunction(&env, src, "f1"); + v8::Local<v8::Function> f2 = CompileFunction(&env, src, "f2"); + v8::Local<v8::Function> f3 = CompileFunction(&env, src, "f3"); + + // Call the function to make sure it is compiled. + v8::Handle<v8::Value> argv[] = { v8::Number::New(1), + v8::Number::New(1), + v8::Number::New(1), + v8::Number::New(1) }; + + // Call all functions to make sure that they are compiled. + f0->Call(env->Global(), 0, NULL); + f1->Call(env->Global(), 0, NULL); + f2->Call(env->Global(), 0, NULL); + f3->Call(env->Global(), 0, NULL); + + // Set the debug break flag. + v8::Debug::DebugBreak(); + + // Call all functions with different argument count. + break_point_hit_count = 0; + for (unsigned int i = 0; i < ARRAY_SIZE(argv); i++) { + f0->Call(env->Global(), i, argv); + f1->Call(env->Global(), i, argv); + f2->Call(env->Global(), i, argv); + f3->Call(env->Global(), i, argv); + } + + // One break for each function called. + CHECK_EQ(4 * ARRAY_SIZE(argv), break_point_hit_count); + + // Get rid of the debug event listener. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +// Test to ensure that JavaScript code keeps running while the debug break +// through the stack limit flag is set but breaks are disabled. +TEST(DisableBreak) { + v8::HandleScope scope; + DebugLocalContext env; + + // Register a debug event listener which sets the break flag and counts. + v8::Debug::SetDebugEventListener(DebugEventCounter); + + // Create a function for testing stepping. + const char* src = "function f() {g()};function g(){i=0; while(i<10){i++}}"; + v8::Local<v8::Function> f = CompileFunction(&env, src, "f"); + + // Set the debug break flag. + v8::Debug::DebugBreak(); + + // Call all functions with different argument count. + break_point_hit_count = 0; + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + + { + v8::Debug::DebugBreak(); + v8::internal::DisableBreak disable_break(true); + f->Call(env->Global(), 0, NULL); + CHECK_EQ(1, break_point_hit_count); + } + + f->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Get rid of the debug event listener. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(); +} + + +static v8::Handle<v8::Array> NamedEnum(const v8::AccessorInfo&) { + v8::Handle<v8::Array> result = v8::Array::New(3); + result->Set(v8::Integer::New(0), v8::String::New("a")); + result->Set(v8::Integer::New(1), v8::String::New("b")); + result->Set(v8::Integer::New(2), v8::String::New("c")); + return result; +} + + +static v8::Handle<v8::Array> IndexedEnum(const v8::AccessorInfo&) { + v8::Handle<v8::Array> result = v8::Array::New(2); + result->Set(v8::Integer::New(0), v8::Number::New(1)); + result->Set(v8::Integer::New(1), v8::Number::New(10)); + return result; +} + + +static v8::Handle<v8::Value> NamedGetter(v8::Local<v8::String> name, + const v8::AccessorInfo& info) { + v8::String::AsciiValue n(name); + if (strcmp(*n, "a") == 0) { + return v8::String::New("AA"); + } else if (strcmp(*n, "b") == 0) { + return v8::String::New("BB"); + } else if (strcmp(*n, "c") == 0) { + return v8::String::New("CC"); + } else { + return v8::Undefined(); + } + + return name; +} + + +static v8::Handle<v8::Value> IndexedGetter(uint32_t index, + const v8::AccessorInfo& info) { + return v8::Number::New(index + 1); +} + + +TEST(InterceptorPropertyMirror) { + // Create a V8 environment with debug access. + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + // Create object with named interceptor. + v8::Handle<v8::ObjectTemplate> named = v8::ObjectTemplate::New(); + named->SetNamedPropertyHandler(NamedGetter, NULL, NULL, NULL, NamedEnum); + env->Global()->Set(v8::String::New("intercepted_named"), + named->NewInstance()); + + // Create object with indexed interceptor. + v8::Handle<v8::ObjectTemplate> indexed = v8::ObjectTemplate::New(); + indexed->SetIndexedPropertyHandler(IndexedGetter, + NULL, + NULL, + NULL, + IndexedEnum); + env->Global()->Set(v8::String::New("intercepted_indexed"), + indexed->NewInstance()); + + // Create object with both named and indexed interceptor. + v8::Handle<v8::ObjectTemplate> both = v8::ObjectTemplate::New(); + both->SetNamedPropertyHandler(NamedGetter, NULL, NULL, NULL, NamedEnum); + both->SetIndexedPropertyHandler(IndexedGetter, NULL, NULL, NULL, IndexedEnum); + env->Global()->Set(v8::String::New("intercepted_both"), both->NewInstance()); + + // Get mirrors for the three objects with interceptor. + CompileRun( + "named_mirror = debug.MakeMirror(intercepted_named);" + "indexed_mirror = debug.MakeMirror(intercepted_indexed);" + "both_mirror = debug.MakeMirror(intercepted_both)"); + CHECK(CompileRun( + "named_mirror instanceof debug.ObjectMirror")->BooleanValue()); + CHECK(CompileRun( + "indexed_mirror instanceof debug.ObjectMirror")->BooleanValue()); + CHECK(CompileRun( + "both_mirror instanceof debug.ObjectMirror")->BooleanValue()); + + // Get the property names from the interceptors + CompileRun( + "named_names = named_mirror.propertyNames();" + "indexed_names = indexed_mirror.propertyNames();" + "both_names = both_mirror.propertyNames()"); + CHECK_EQ(3, CompileRun("named_names.length")->Int32Value()); + CHECK_EQ(2, CompileRun("indexed_names.length")->Int32Value()); + CHECK_EQ(5, CompileRun("both_names.length")->Int32Value()); + + // Check the expected number of properties. + const char* source; + source = "named_mirror.properties().length"; + CHECK_EQ(3, CompileRun(source)->Int32Value()); + + source = "indexed_mirror.properties().length"; + CHECK_EQ(2, CompileRun(source)->Int32Value()); + + source = "both_mirror.properties().length"; + CHECK_EQ(5, CompileRun(source)->Int32Value()); + + // 1 is PropertyKind.Named; + source = "both_mirror.properties(1).length"; + CHECK_EQ(3, CompileRun(source)->Int32Value()); + + // 2 is PropertyKind.Indexed; + source = "both_mirror.properties(2).length"; + CHECK_EQ(2, CompileRun(source)->Int32Value()); + + // 3 is PropertyKind.Named | PropertyKind.Indexed; + source = "both_mirror.properties(3).length"; + CHECK_EQ(5, CompileRun(source)->Int32Value()); + + // Get the interceptor properties for the object with only named interceptor. + CompileRun("named_values = named_mirror.properties()"); + + // Check that the properties are interceptor properties. + for (int i = 0; i < 3; i++) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + OS::SNPrintF(buffer, + "named_values[%d] instanceof debug.PropertyMirror", i); + CHECK(CompileRun(buffer.start())->BooleanValue()); + + // 4 is PropertyType.Interceptor + OS::SNPrintF(buffer, "named_values[%d].propertyType()", i); + CHECK_EQ(4, CompileRun(buffer.start())->Int32Value()); + + OS::SNPrintF(buffer, "named_values[%d].isNative()", i); + CHECK(CompileRun(buffer.start())->BooleanValue()); + } + + // Get the interceptor properties for the object with only indexed + // interceptor. + CompileRun("indexed_values = indexed_mirror.properties()"); + + // Check that the properties are interceptor properties. + for (int i = 0; i < 2; i++) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + OS::SNPrintF(buffer, + "indexed_values[%d] instanceof debug.PropertyMirror", i); + CHECK(CompileRun(buffer.start())->BooleanValue()); + } + + // Get the interceptor properties for the object with both types of + // interceptors. + CompileRun("both_values = both_mirror.properties()"); + + // Check that the properties are interceptor properties. + for (int i = 0; i < 5; i++) { + EmbeddedVector<char, SMALL_STRING_BUFFER_SIZE> buffer; + OS::SNPrintF(buffer, "both_values[%d] instanceof debug.PropertyMirror", i); + CHECK(CompileRun(buffer.start())->BooleanValue()); + } + + // Check the property names. + source = "both_values[0].name() == 'a'"; + CHECK(CompileRun(source)->BooleanValue()); + + source = "both_values[1].name() == 'b'"; + CHECK(CompileRun(source)->BooleanValue()); + + source = "both_values[2].name() == 'c'"; + CHECK(CompileRun(source)->BooleanValue()); + + source = "both_values[3].name() == 1"; + CHECK(CompileRun(source)->BooleanValue()); + + source = "both_values[4].name() == 10"; + CHECK(CompileRun(source)->BooleanValue()); +} + + +TEST(HiddenPrototypePropertyMirror) { + // Create a V8 environment with debug access. + v8::HandleScope scope; + DebugLocalContext env; + env.ExposeDebug(); + + v8::Handle<v8::FunctionTemplate> t0 = v8::FunctionTemplate::New(); + t0->InstanceTemplate()->Set(v8::String::New("x"), v8::Number::New(0)); + v8::Handle<v8::FunctionTemplate> t1 = v8::FunctionTemplate::New(); + t1->SetHiddenPrototype(true); + t1->InstanceTemplate()->Set(v8::String::New("y"), v8::Number::New(1)); + v8::Handle<v8::FunctionTemplate> t2 = v8::FunctionTemplate::New(); + t2->SetHiddenPrototype(true); + t2->InstanceTemplate()->Set(v8::String::New("z"), v8::Number::New(2)); + v8::Handle<v8::FunctionTemplate> t3 = v8::FunctionTemplate::New(); + t3->InstanceTemplate()->Set(v8::String::New("u"), v8::Number::New(3)); + + // Create object and set them on the global object. + v8::Handle<v8::Object> o0 = t0->GetFunction()->NewInstance(); + env->Global()->Set(v8::String::New("o0"), o0); + v8::Handle<v8::Object> o1 = t1->GetFunction()->NewInstance(); + env->Global()->Set(v8::String::New("o1"), o1); + v8::Handle<v8::Object> o2 = t2->GetFunction()->NewInstance(); + env->Global()->Set(v8::String::New("o2"), o2); + v8::Handle<v8::Object> o3 = t3->GetFunction()->NewInstance(); + env->Global()->Set(v8::String::New("o3"), o3); + + // Get mirrors for the four objects. + CompileRun( + "o0_mirror = debug.MakeMirror(o0);" + "o1_mirror = debug.MakeMirror(o1);" + "o2_mirror = debug.MakeMirror(o2);" + "o3_mirror = debug.MakeMirror(o3)"); + CHECK(CompileRun("o0_mirror instanceof debug.ObjectMirror")->BooleanValue()); + CHECK(CompileRun("o1_mirror instanceof debug.ObjectMirror")->BooleanValue()); + CHECK(CompileRun("o2_mirror instanceof debug.ObjectMirror")->BooleanValue()); + CHECK(CompileRun("o3_mirror instanceof debug.ObjectMirror")->BooleanValue()); + + // Check that each object has one property. + CHECK_EQ(1, CompileRun( + "o0_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o1_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o2_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o3_mirror.propertyNames().length")->Int32Value()); + + // Set o1 as prototype for o0. o1 has the hidden prototype flag so all + // properties on o1 should be seen on o0. + o0->Set(v8::String::New("__proto__"), o1); + CHECK_EQ(2, CompileRun( + "o0_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(0, CompileRun( + "o0_mirror.property('x').value().value()")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o0_mirror.property('y').value().value()")->Int32Value()); + + // Set o2 as prototype for o0 (it will end up after o1 as o1 has the hidden + // prototype flag. o2 also has the hidden prototype flag so all properties + // on o2 should be seen on o0 as well as properties on o1. + o0->Set(v8::String::New("__proto__"), o2); + CHECK_EQ(3, CompileRun( + "o0_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(0, CompileRun( + "o0_mirror.property('x').value().value()")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o0_mirror.property('y').value().value()")->Int32Value()); + CHECK_EQ(2, CompileRun( + "o0_mirror.property('z').value().value()")->Int32Value()); + + // Set o3 as prototype for o0 (it will end up after o1 and o2 as both o1 and + // o2 has the hidden prototype flag. o3 does not have the hidden prototype + // flag so properties on o3 should not be seen on o0 whereas the properties + // from o1 and o2 should still be seen on o0. + // Final prototype chain: o0 -> o1 -> o2 -> o3 + // Hidden prototypes: ^^ ^^ + o0->Set(v8::String::New("__proto__"), o3); + CHECK_EQ(3, CompileRun( + "o0_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o3_mirror.propertyNames().length")->Int32Value()); + CHECK_EQ(0, CompileRun( + "o0_mirror.property('x').value().value()")->Int32Value()); + CHECK_EQ(1, CompileRun( + "o0_mirror.property('y').value().value()")->Int32Value()); + CHECK_EQ(2, CompileRun( + "o0_mirror.property('z').value().value()")->Int32Value()); + CHECK(CompileRun("o0_mirror.property('u').isUndefined()")->BooleanValue()); + + // The prototype (__proto__) for o0 should be o3 as o1 and o2 are hidden. + CHECK(CompileRun("o0_mirror.protoObject() == o3_mirror")->BooleanValue()); +} + + +// Multithreaded tests of JSON debugger protocol + +// Support classes + +// Copies a C string to a 16-bit string. Does not check for buffer overflow. +// Does not use the V8 engine to convert strings, so it can be used +// in any thread. Returns the length of the string. +int AsciiToUtf16(const char* input_buffer, uint16_t* output_buffer) { + int i; + for (i = 0; input_buffer[i] != '\0'; ++i) { + // ASCII does not use chars > 127, but be careful anyway. + output_buffer[i] = static_cast<unsigned char>(input_buffer[i]); + } + output_buffer[i] = 0; + return i; +} + +// Copies a 16-bit string to a C string by dropping the high byte of +// each character. Does not check for buffer overflow. +// Can be used in any thread. Requires string length as an input. +int Utf16ToAscii(const uint16_t* input_buffer, int length, + char* output_buffer) { + for (int i = 0; i < length; ++i) { + output_buffer[i] = static_cast<char>(input_buffer[i]); + } + output_buffer[length] = '\0'; + return length; +} + +// Provides synchronization between k threads, where k is an input to the +// constructor. The Wait() call blocks a thread until it is called for the +// k'th time, then all calls return. Each ThreadBarrier object can only +// be used once. +class ThreadBarrier { + public: + explicit ThreadBarrier(int num_threads); + ~ThreadBarrier(); + void Wait(); + private: + int num_threads_; + int num_blocked_; + v8::internal::Mutex* lock_; + v8::internal::Semaphore* sem_; + bool invalid_; +}; + +ThreadBarrier::ThreadBarrier(int num_threads) + : num_threads_(num_threads), num_blocked_(0) { + lock_ = OS::CreateMutex(); + sem_ = OS::CreateSemaphore(0); + invalid_ = false; // A barrier may only be used once. Then it is invalid. +} + +// Do not call, due to race condition with Wait(). +// Could be resolved with Pthread condition variables. +ThreadBarrier::~ThreadBarrier() { + lock_->Lock(); + delete lock_; + delete sem_; +} + +void ThreadBarrier::Wait() { + lock_->Lock(); + ASSERT(!invalid_); + if (num_blocked_ == num_threads_ - 1) { + // Signal and unblock all waiting threads. + for (int i = 0; i < num_threads_ - 1; ++i) { + sem_->Signal(); + } + invalid_ = true; + printf("BARRIER\n\n"); + fflush(stdout); + lock_->Unlock(); + } else { // Wait for the semaphore. + ++num_blocked_; + lock_->Unlock(); // Potential race condition with destructor because + sem_->Wait(); // these two lines are not atomic. + } +} + +// A set containing enough barriers and semaphores for any of the tests. +class Barriers { + public: + Barriers(); + void Initialize(); + ThreadBarrier barrier_1; + ThreadBarrier barrier_2; + ThreadBarrier barrier_3; + ThreadBarrier barrier_4; + ThreadBarrier barrier_5; + v8::internal::Semaphore* semaphore_1; + v8::internal::Semaphore* semaphore_2; +}; + +Barriers::Barriers() : barrier_1(2), barrier_2(2), + barrier_3(2), barrier_4(2), barrier_5(2) {} + +void Barriers::Initialize() { + semaphore_1 = OS::CreateSemaphore(0); + semaphore_2 = OS::CreateSemaphore(0); +} + + +// We match parts of the message to decide if it is a break message. +bool IsBreakEventMessage(char *message) { + const char* type_event = "\"type\":\"event\""; + const char* event_break = "\"event\":\"break\""; + // Does the message contain both type:event and event:break? + return strstr(message, type_event) != NULL && + strstr(message, event_break) != NULL; +} + + +/* Test MessageQueues */ +/* Tests the message queues that hold debugger commands and + * response messages to the debugger. Fills queues and makes + * them grow. + */ +Barriers message_queue_barriers; + +// This is the debugger thread, that executes no v8 calls except +// placing JSON debugger commands in the queue. +class MessageQueueDebuggerThread : public v8::internal::Thread { + public: + void Run(); +}; + +static void MessageHandler(const uint16_t* message, int length, void *data) { + static char print_buffer[1000]; + Utf16ToAscii(message, length, print_buffer); + if (IsBreakEventMessage(print_buffer)) { + // Lets test script wait until break occurs to send commands. + // Signals when a break is reported. + message_queue_barriers.semaphore_2->Signal(); + } + // Allow message handler to block on a semaphore, to test queueing of + // messages while blocked. + message_queue_barriers.semaphore_1->Wait(); + printf("%s\n", print_buffer); + fflush(stdout); +} + + +void MessageQueueDebuggerThread::Run() { + const int kBufferSize = 1000; + uint16_t buffer_1[kBufferSize]; + uint16_t buffer_2[kBufferSize]; + const char* command_1 = + "{\"seq\":117," + "\"type\":\"request\"," + "\"command\":\"evaluate\"," + "\"arguments\":{\"expression\":\"1+2\"}}"; + const char* command_2 = + "{\"seq\":118," + "\"type\":\"request\"," + "\"command\":\"evaluate\"," + "\"arguments\":{\"expression\":\"1+a\"}}"; + const char* command_3 = + "{\"seq\":119," + "\"type\":\"request\"," + "\"command\":\"evaluate\"," + "\"arguments\":{\"expression\":\"c.d * b\"}}"; + const char* command_continue = + "{\"seq\":106," + "\"type\":\"request\"," + "\"command\":\"continue\"}"; + const char* command_single_step = + "{\"seq\":107," + "\"type\":\"request\"," + "\"command\":\"continue\"," + "\"arguments\":{\"stepaction\":\"next\"}}"; + + /* Interleaved sequence of actions by the two threads:*/ + // Main thread compiles and runs source_1 + message_queue_barriers.barrier_1.Wait(); + // Post 6 commands, filling the command queue and making it expand. + // These calls return immediately, but the commands stay on the queue + // until the execution of source_2. + // Note: AsciiToUtf16 executes before SendCommand, so command is copied + // to buffer before buffer is sent to SendCommand. + v8::Debug::SendCommand(buffer_1, AsciiToUtf16(command_1, buffer_1)); + v8::Debug::SendCommand(buffer_2, AsciiToUtf16(command_2, buffer_2)); + v8::Debug::SendCommand(buffer_2, AsciiToUtf16(command_3, buffer_2)); + v8::Debug::SendCommand(buffer_2, AsciiToUtf16(command_3, buffer_2)); + v8::Debug::SendCommand(buffer_2, AsciiToUtf16(command_3, buffer_2)); + message_queue_barriers.barrier_2.Wait(); + // Main thread compiles and runs source_2. + // Queued commands are executed at the start of compilation of source_2. + message_queue_barriers.barrier_3.Wait(); + // Free the message handler to process all the messages from the queue. + for (int i = 0; i < 20 ; ++i) { + message_queue_barriers.semaphore_1->Signal(); + } + // Main thread compiles and runs source_3. + // source_3 includes a debugger statement, which causes a break event. + // Wait on break event from hitting "debugger" statement + message_queue_barriers.semaphore_2->Wait(); + // These should execute after the "debugger" statement in source_2 + v8::Debug::SendCommand(buffer_2, AsciiToUtf16(command_single_step, buffer_2)); + // Wait on break event after a single step executes. + message_queue_barriers.semaphore_2->Wait(); + v8::Debug::SendCommand(buffer_1, AsciiToUtf16(command_2, buffer_1)); + v8::Debug::SendCommand(buffer_2, AsciiToUtf16(command_continue, buffer_2)); + // Main thread continues running source_3 to end, waits for this thread. +} + +MessageQueueDebuggerThread message_queue_debugger_thread; + +// This thread runs the v8 engine. +TEST(MessageQueues) { + // Create a V8 environment + v8::HandleScope scope; + DebugLocalContext env; + message_queue_barriers.Initialize(); + v8::Debug::SetMessageHandler(MessageHandler); + message_queue_debugger_thread.Start(); + + const char* source_1 = "a = 3; b = 4; c = new Object(); c.d = 5;"; + const char* source_2 = "e = 17;"; + const char* source_3 = "a = 4; debugger; a = 5; a = 6; a = 7;"; + + // See MessageQueueDebuggerThread::Run for interleaved sequence of + // API calls and events in the two threads. + CompileRun(source_1); + message_queue_barriers.barrier_1.Wait(); + message_queue_barriers.barrier_2.Wait(); + CompileRun(source_2); + message_queue_barriers.barrier_3.Wait(); + CompileRun(source_3); + message_queue_debugger_thread.Join(); + fflush(stdout); +} + +/* Test ThreadedDebugging */ +/* This test interrupts a running infinite loop that is + * occupying the v8 thread by a break command from the + * debugger thread. It then changes the value of a + * global object, to make the loop terminate. + */ + +Barriers threaded_debugging_barriers; + +class V8Thread : public v8::internal::Thread { + public: + void Run(); +}; + +class DebuggerThread : public v8::internal::Thread { + public: + void Run(); +}; + + +static void ThreadedMessageHandler(const uint16_t* message, int length, + void *data) { + static char print_buffer[1000]; + Utf16ToAscii(message, length, print_buffer); + if (IsBreakEventMessage(print_buffer)) { + threaded_debugging_barriers.barrier_2.Wait(); + } + printf("%s\n", print_buffer); + fflush(stdout); +} + + +void V8Thread::Run() { + const char* source_1 = + "flag = true;\n" + "function bar( new_value ) {\n" + " flag = new_value;\n" + " return \"Return from bar(\" + new_value + \")\";\n" + "}\n" + "\n" + "function foo() {\n" + " var x = 1;\n" + " while ( flag == true ) {\n" + " x = x + 1;\n" + " }\n" + "}\n" + "\n"; + const char* source_2 = "foo();\n"; + + v8::HandleScope scope; + DebugLocalContext env; + v8::Debug::SetMessageHandler(&ThreadedMessageHandler); + + CompileRun(source_1); + threaded_debugging_barriers.barrier_1.Wait(); + CompileRun(source_2); +} + +void DebuggerThread::Run() { + const int kBufSize = 1000; + uint16_t buffer[kBufSize]; + + const char* command_1 = "{\"seq\":102," + "\"type\":\"request\"," + "\"command\":\"evaluate\"," + "\"arguments\":{\"expression\":\"bar(false)\"}}"; + const char* command_2 = "{\"seq\":103," + "\"type\":\"request\"," + "\"command\":\"continue\"}"; + + threaded_debugging_barriers.barrier_1.Wait(); + v8::Debug::DebugBreak(); + threaded_debugging_barriers.barrier_2.Wait(); + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_1, buffer)); + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_2, buffer)); +} + +DebuggerThread debugger_thread; +V8Thread v8_thread; + +TEST(ThreadedDebugging) { + // Create a V8 environment + threaded_debugging_barriers.Initialize(); + + v8_thread.Start(); + debugger_thread.Start(); + + v8_thread.Join(); + debugger_thread.Join(); +} + +/* Test RecursiveBreakpoints */ +/* In this test, the debugger evaluates a function with a breakpoint, after + * hitting a breakpoint in another function. We do this with both values + * of the flag enabling recursive breakpoints, and verify that the second + * breakpoint is hit when enabled, and missed when disabled. + */ + +class BreakpointsV8Thread : public v8::internal::Thread { + public: + void Run(); +}; + +class BreakpointsDebuggerThread : public v8::internal::Thread { + public: + void Run(); +}; + + +Barriers* breakpoints_barriers; + +static void BreakpointsMessageHandler(const uint16_t* message, + int length, + void *data) { + static char print_buffer[1000]; + Utf16ToAscii(message, length, print_buffer); + printf("%s\n", print_buffer); + fflush(stdout); + + // Is break_template a prefix of the message? + if (IsBreakEventMessage(print_buffer)) { + breakpoints_barriers->semaphore_1->Signal(); + } +} + + +void BreakpointsV8Thread::Run() { + const char* source_1 = "var y_global = 3;\n" + "function cat( new_value ) {\n" + " var x = new_value;\n" + " y_global = 4;\n" + " x = 3 * x + 1;\n" + " y_global = 5;\n" + " return x;\n" + "}\n" + "\n" + "function dog() {\n" + " var x = 1;\n" + " x = y_global;" + " var z = 3;" + " x += 100;\n" + " return x;\n" + "}\n" + "\n"; + const char* source_2 = "cat(17);\n" + "cat(19);\n"; + + v8::HandleScope scope; + DebugLocalContext env; + v8::Debug::SetMessageHandler(&BreakpointsMessageHandler); + + CompileRun(source_1); + breakpoints_barriers->barrier_1.Wait(); + breakpoints_barriers->barrier_2.Wait(); + CompileRun(source_2); +} + + +void BreakpointsDebuggerThread::Run() { + const int kBufSize = 1000; + uint16_t buffer[kBufSize]; + + const char* command_1 = "{\"seq\":101," + "\"type\":\"request\"," + "\"command\":\"setbreakpoint\"," + "\"arguments\":{\"type\":\"function\",\"target\":\"cat\",\"line\":3}}"; + const char* command_2 = "{\"seq\":102," + "\"type\":\"request\"," + "\"command\":\"setbreakpoint\"," + "\"arguments\":{\"type\":\"function\",\"target\":\"dog\",\"line\":3}}"; + const char* command_3 = "{\"seq\":104," + "\"type\":\"request\"," + "\"command\":\"evaluate\"," + "\"arguments\":{\"expression\":\"dog()\",\"disable_break\":false}}"; + const char* command_4 = "{\"seq\":105," + "\"type\":\"request\"," + "\"command\":\"evaluate\"," + "\"arguments\":{\"expression\":\"x\",\"disable_break\":true}}"; + const char* command_5 = "{\"seq\":106," + "\"type\":\"request\"," + "\"command\":\"continue\"}"; + const char* command_6 = "{\"seq\":107," + "\"type\":\"request\"," + "\"command\":\"continue\"}"; + const char* command_7 = "{\"seq\":108," + "\"type\":\"request\"," + "\"command\":\"evaluate\"," + "\"arguments\":{\"expression\":\"dog()\",\"disable_break\":true}}"; + const char* command_8 = "{\"seq\":109," + "\"type\":\"request\"," + "\"command\":\"continue\"}"; + + + // v8 thread initializes, runs source_1 + breakpoints_barriers->barrier_1.Wait(); + // 1:Set breakpoint in cat(). + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_1, buffer)); + // 2:Set breakpoint in dog() + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_2, buffer)); + breakpoints_barriers->barrier_2.Wait(); + // v8 thread starts compiling source_2. + // Automatic break happens, to run queued commands + // breakpoints_barriers->semaphore_1->Wait(); + // Commands 1 through 3 run, thread continues. + // v8 thread runs source_2 to breakpoint in cat(). + // message callback receives break event. + breakpoints_barriers->semaphore_1->Wait(); + // 4:Evaluate dog() (which has a breakpoint). + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_3, buffer)); + // v8 thread hits breakpoint in dog() + breakpoints_barriers->semaphore_1->Wait(); // wait for break event + // 5:Evaluate x + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_4, buffer)); + // 6:Continue evaluation of dog() + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_5, buffer)); + // dog() finishes. + // 7:Continue evaluation of source_2, finish cat(17), hit breakpoint + // in cat(19). + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_6, buffer)); + // message callback gets break event + breakpoints_barriers->semaphore_1->Wait(); // wait for break event + // 8: Evaluate dog() with breaks disabled + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_7, buffer)); + // 9: Continue evaluation of source2, reach end. + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_8, buffer)); +} + +BreakpointsDebuggerThread breakpoints_debugger_thread; +BreakpointsV8Thread breakpoints_v8_thread; + +TEST(RecursiveBreakpoints) { + i::FLAG_debugger_auto_break = true; + + // Create a V8 environment + Barriers stack_allocated_breakpoints_barriers; + stack_allocated_breakpoints_barriers.Initialize(); + breakpoints_barriers = &stack_allocated_breakpoints_barriers; + + breakpoints_v8_thread.Start(); + breakpoints_debugger_thread.Start(); + + breakpoints_v8_thread.Join(); + breakpoints_debugger_thread.Join(); +} + + +static void DummyDebugEventListener(v8::DebugEvent event, + v8::Handle<v8::Object> exec_state, + v8::Handle<v8::Object> event_data, + v8::Handle<v8::Value> data) { +} + + +TEST(SetDebugEventListenerOnUninitializedVM) { + v8::Debug::SetDebugEventListener(DummyDebugEventListener); +} + + +static void DummyMessageHandler(const uint16_t* message, + int length, void *data) { +} + + +TEST(SetMessageHandlerOnUninitializedVM) { + v8::Debug::SetMessageHandler(DummyMessageHandler); +} + + +TEST(DebugBreakOnUninitializedVM) { + v8::Debug::DebugBreak(); +} + + +TEST(SendCommandToUninitializedVM) { + const char* dummy_command = "{}"; + uint16_t dummy_buffer[80]; + int dummy_length = AsciiToUtf16(dummy_command, dummy_buffer); + v8::Debug::SendCommand(dummy_buffer, dummy_length); +} + + +// Source for a JavaScript function which returns the source line for the top +// frame. +static const char* frame_source_line_source = + "function frame_source_line(exec_state) {" + " return exec_state.frame(0).sourceLine();" + "}"; +v8::Handle<v8::Function> frame_source_line; + + +// Source for a JavaScript function which returns the data parameter of a +// function called in the context of the debugger. If no data parameter is +// passed it throws an exception. +static const char* debugger_call_with_data_source = + "function debugger_call_with_data(exec_state, data) {" + " if (data) return data;" + " throw 'No data!'" + "}"; +v8::Handle<v8::Function> debugger_call_with_data; + + +// Source for a JavaScript function which returns the data parameter of a +// function called in the context of the debugger. If no data parameter is +// passed it throws an exception. +static const char* debugger_call_with_closure_source = + "var x = 3;" + "function (exec_state) {" + " if (exec_state.y) return x - 1;" + " exec_state.y = x;" + " return exec_state.y" + "}"; +v8::Handle<v8::Function> debugger_call_with_closure; + +// Function to retrieve the number of JavaScript frames by calling a JavaScript +// in the debugger. +static v8::Handle<v8::Value> CheckFrameCount(const v8::Arguments& args) { + CHECK(v8::Debug::Call(frame_count)->IsNumber()); + CHECK_EQ(args[0]->Int32Value(), + v8::Debug::Call(frame_count)->Int32Value()); + return v8::Undefined(); +} + + +// Function to retrieve the source line of the top JavaScript frame by calling a +// JavaScript function in the debugger. +static v8::Handle<v8::Value> CheckSourceLine(const v8::Arguments& args) { + CHECK(v8::Debug::Call(frame_source_line)->IsNumber()); + CHECK_EQ(args[0]->Int32Value(), + v8::Debug::Call(frame_source_line)->Int32Value()); + return v8::Undefined(); +} + + +// Function to test passing an additional parameter to a JavaScript function +// called in the debugger. It also tests that functions called in the debugger +// can throw exceptions. +static v8::Handle<v8::Value> CheckDataParameter(const v8::Arguments& args) { + v8::Handle<v8::String> data = v8::String::New("Test"); + CHECK(v8::Debug::Call(debugger_call_with_data, data)->IsString()); + + CHECK(v8::Debug::Call(debugger_call_with_data).IsEmpty()); + CHECK(v8::Debug::Call(debugger_call_with_data).IsEmpty()); + + v8::TryCatch catcher; + v8::Debug::Call(debugger_call_with_data); + CHECK(catcher.HasCaught()); + CHECK(catcher.Exception()->IsString()); + + return v8::Undefined(); +} + + +// Function to test using a JavaScript with closure in the debugger. +static v8::Handle<v8::Value> CheckClosure(const v8::Arguments& args) { + CHECK(v8::Debug::Call(debugger_call_with_closure)->IsNumber()); + CHECK_EQ(3, v8::Debug::Call(debugger_call_with_closure)->Int32Value()); + return v8::Undefined(); +} + + +// Test functions called through the debugger. +TEST(CallFunctionInDebugger) { + // Create and enter a context with the functions CheckFrameCount, + // CheckSourceLine and CheckDataParameter installed. + v8::HandleScope scope; + v8::Handle<v8::ObjectTemplate> global_template = v8::ObjectTemplate::New(); + global_template->Set(v8::String::New("CheckFrameCount"), + v8::FunctionTemplate::New(CheckFrameCount)); + global_template->Set(v8::String::New("CheckSourceLine"), + v8::FunctionTemplate::New(CheckSourceLine)); + global_template->Set(v8::String::New("CheckDataParameter"), + v8::FunctionTemplate::New(CheckDataParameter)); + global_template->Set(v8::String::New("CheckClosure"), + v8::FunctionTemplate::New(CheckClosure)); + v8::Handle<v8::Context> context = v8::Context::New(NULL, global_template); + v8::Context::Scope context_scope(context); + + // Compile a function for checking the number of JavaScript frames. + v8::Script::Compile(v8::String::New(frame_count_source))->Run(); + frame_count = v8::Local<v8::Function>::Cast( + context->Global()->Get(v8::String::New("frame_count"))); + + // Compile a function for returning the source line for the top frame. + v8::Script::Compile(v8::String::New(frame_source_line_source))->Run(); + frame_source_line = v8::Local<v8::Function>::Cast( + context->Global()->Get(v8::String::New("frame_source_line"))); + + // Compile a function returning the data parameter. + v8::Script::Compile(v8::String::New(debugger_call_with_data_source))->Run(); + debugger_call_with_data = v8::Local<v8::Function>::Cast( + context->Global()->Get(v8::String::New("debugger_call_with_data"))); + + // Compile a function capturing closure. + debugger_call_with_closure = v8::Local<v8::Function>::Cast( + v8::Script::Compile( + v8::String::New(debugger_call_with_closure_source))->Run()); + + // Calling a function through the debugger returns undefined if there are no + // JavaScript frames. + CHECK(v8::Debug::Call(frame_count)->IsUndefined()); + CHECK(v8::Debug::Call(frame_source_line)->IsUndefined()); + CHECK(v8::Debug::Call(debugger_call_with_data)->IsUndefined()); + + // Test that the number of frames can be retrieved. + v8::Script::Compile(v8::String::New("CheckFrameCount(1)"))->Run(); + v8::Script::Compile(v8::String::New("function f() {" + " CheckFrameCount(2);" + "}; f()"))->Run(); + + // Test that the source line can be retrieved. + v8::Script::Compile(v8::String::New("CheckSourceLine(0)"))->Run(); + v8::Script::Compile(v8::String::New("function f() {\n" + " CheckSourceLine(1)\n" + " CheckSourceLine(2)\n" + " CheckSourceLine(3)\n" + "}; f()"))->Run(); + + // Test that a parameter can be passed to a function called in the debugger. + v8::Script::Compile(v8::String::New("CheckDataParameter()"))->Run(); + + // Test that a function with closure can be run in the debugger. + v8::Script::Compile(v8::String::New("CheckClosure()"))->Run(); + + + // Test that the source line is correct when there is a line offset. + v8::ScriptOrigin origin(v8::String::New("test"), + v8::Integer::New(7)); + v8::Script::Compile(v8::String::New("CheckSourceLine(7)"), &origin)->Run(); + v8::Script::Compile(v8::String::New("function f() {\n" + " CheckSourceLine(8)\n" + " CheckSourceLine(9)\n" + " CheckSourceLine(10)\n" + "}; f()"), &origin)->Run(); +} + + +// Test that clearing the debug event listener actually clears all break points +// and related information. +TEST(DebuggerUnload) { + v8::HandleScope scope; + DebugLocalContext env; + + // Check debugger is unloaded before it is used. + CheckDebuggerUnloaded(); + + // Add debug event listener. + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + // Create a couple of functions for the test. + v8::Local<v8::Function> foo = + CompileFunction(&env, "function foo(){x=1}", "foo"); + v8::Local<v8::Function> bar = + CompileFunction(&env, "function bar(){y=2}", "bar"); + + // Set some break points. + SetBreakPoint(foo, 0); + SetBreakPoint(foo, 4); + SetBreakPoint(bar, 0); + SetBreakPoint(bar, 4); + + // Make sure that the break points are there. + break_point_hit_count = 0; + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + bar->Call(env->Global(), 0, NULL); + CHECK_EQ(4, break_point_hit_count); + + // Remove the debug event listener without clearing breakpoints. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(true); + + // Set a new debug event listener. + v8::Debug::SetDebugEventListener(DebugEventBreakPointHitCount, + v8::Undefined()); + // Check that the break points was actually cleared. + break_point_hit_count = 0; + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(0, break_point_hit_count); + + // Set break points and run again. + SetBreakPoint(foo, 0); + SetBreakPoint(foo, 4); + foo->Call(env->Global(), 0, NULL); + CHECK_EQ(2, break_point_hit_count); + + // Remove the debug event listener without clearing breakpoints again. + v8::Debug::SetDebugEventListener(NULL); + CheckDebuggerUnloaded(true); +} + + +// Debugger message handler which counts the number of times it is called. +static int message_handler_hit_count = 0; +static void MessageHandlerHitCount(const uint16_t* message, + int length, void* data) { + message_handler_hit_count++; + + const int kBufferSize = 1000; + uint16_t buffer[kBufferSize]; + const char* command_continue = + "{\"seq\":0," + "\"type\":\"request\"," + "\"command\":\"continue\"}"; + + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_continue, buffer)); +} + + +// Test clearing the debug message handler. +TEST(DebuggerClearMessageHandler) { + v8::HandleScope scope; + DebugLocalContext env; + + // Check debugger is unloaded before it is used. + CheckDebuggerUnloaded(); + + // Set a debug message handler. + v8::Debug::SetMessageHandler(MessageHandlerHitCount); + + // Run code to throw a unhandled exception. This should end up in the message + // handler. + CompileRun("throw 1"); + + // The message handler should be called. + CHECK_GT(message_handler_hit_count, 0); + + // Clear debug message handler. + message_handler_hit_count = 0; + v8::Debug::SetMessageHandler(NULL); + + // Run code to throw a unhandled exception. This should end up in the message + // handler. + CompileRun("throw 1"); + + // The message handler should not be called more. + CHECK_EQ(0, message_handler_hit_count); + + CheckDebuggerUnloaded(true); +} + + +// Debugger message handler which clears the message handler while active. +static void MessageHandlerClearingMessageHandler(const uint16_t* message, + int length, + void* data) { + message_handler_hit_count++; + + // Clear debug message handler. + v8::Debug::SetMessageHandler(NULL); +} + + +// Test clearing the debug message handler while processing a debug event. +TEST(DebuggerClearMessageHandlerWhileActive) { + v8::HandleScope scope; + DebugLocalContext env; + + // Check debugger is unloaded before it is used. + CheckDebuggerUnloaded(); + + // Set a debug message handler. + v8::Debug::SetMessageHandler(MessageHandlerClearingMessageHandler); + + // Run code to throw a unhandled exception. This should end up in the message + // handler. + CompileRun("throw 1"); + + // The message handler should be called. + CHECK_EQ(1, message_handler_hit_count); + + CheckDebuggerUnloaded(true); +} + + +int host_dispatch_hit_count = 0; +static void HostDispatchHandlerHitCount(void* dispatch, void *data) { + host_dispatch_hit_count++; +} + + +// Test that clearing the debug event listener actually clears all break points +// and related information. +TEST(DebuggerHostDispatch) { + i::FLAG_debugger_auto_break = true; + + v8::HandleScope scope; + DebugLocalContext env; + + const int kBufferSize = 1000; + uint16_t buffer[kBufferSize]; + const char* command_continue = + "{\"seq\":0," + "\"type\":\"request\"," + "\"command\":\"continue\"}"; + + // Setup message and host dispatch handlers. + v8::Debug::SetMessageHandler(DummyMessageHandler); + v8::Debug::SetHostDispatchHandler(HostDispatchHandlerHitCount, + NULL); + + // Fill a host dispatch and a continue command on the command queue before + // running some code. + v8::Debug::SendHostDispatch(NULL); + v8::Debug::SendCommand(buffer, AsciiToUtf16(command_continue, buffer)); + CompileRun("void 0"); + + // The host dispatch callback should be called. + CHECK_EQ(1, host_dispatch_hit_count); +} + + +TEST(DebuggerAgent) { + // Make sure this port is not used by other tests to allow tests to run in + // parallel. + const int kPort = 5858; + + // Make a string with the port number. + const int kPortBufferLen = 6; + char port_str[kPortBufferLen]; + OS::SNPrintF(i::Vector<char>(port_str, kPortBufferLen), "%d", kPort); + + bool ok; + + // Initialize the socket library. + i::Socket::Setup(); + + // Test starting and stopping the agent without any client connection. + i::Debugger::StartAgent("test", kPort); + i::Debugger::StopAgent(); + + // Test starting the agent, connecting a client and shutting down the agent + // with the client connected. + ok = i::Debugger::StartAgent("test", kPort); + CHECK(ok); + i::Socket* client = i::OS::CreateSocket(); + ok = client->Connect("localhost", port_str); + CHECK(ok); + i::Debugger::StopAgent(); + delete client; + + // Test starting and stopping the agent with the required port already + // occoupied. + i::Socket* server = i::OS::CreateSocket(); + server->Bind(kPort); + + i::Debugger::StartAgent("test", kPort); + i::Debugger::StopAgent(); + + delete server; +} + + +class DebuggerAgentProtocolServerThread : public i::Thread { + public: + explicit DebuggerAgentProtocolServerThread(int port) + : port_(port), server_(NULL), client_(NULL), + listening_(OS::CreateSemaphore(0)) { + } + ~DebuggerAgentProtocolServerThread() { + // Close both sockets. + delete client_; + delete server_; + delete listening_; + } + + void Run(); + void WaitForListening() { listening_->Wait(); } + char* body() { return *body_; } + + private: + int port_; + i::SmartPointer<char> body_; + i::Socket* server_; // Server socket used for bind/accept. + i::Socket* client_; // Single client connection used by the test. + i::Semaphore* listening_; // Signalled when the server is in listen mode. +}; + + +void DebuggerAgentProtocolServerThread::Run() { + bool ok; + + // Create the server socket and bind it to the requested port. + server_ = i::OS::CreateSocket(); + CHECK(server_ != NULL); + ok = server_->Bind(port_); + CHECK(ok); + + // Listen for new connections. + ok = server_->Listen(1); + CHECK(ok); + listening_->Signal(); + + // Accept a connection. + client_ = server_->Accept(); + CHECK(client_ != NULL); + + // Receive a debugger agent protocol message. + i::DebuggerAgentUtil::ReceiveMessage(client_); +} + + +TEST(DebuggerAgentProtocolOverflowHeader) { + // Make sure this port is not used by other tests to allow tests to run in + // parallel. + const int kPort = 5860; + static const char* kLocalhost = "localhost"; + + // Make a string with the port number. + const int kPortBufferLen = 6; + char port_str[kPortBufferLen]; + OS::SNPrintF(i::Vector<char>(port_str, kPortBufferLen), "%d", kPort); + + // Initialize the socket library. + i::Socket::Setup(); + + // Create a socket server to receive a debugger agent message. + DebuggerAgentProtocolServerThread* server = + new DebuggerAgentProtocolServerThread(kPort); + server->Start(); + server->WaitForListening(); + + // Connect. + i::Socket* client = i::OS::CreateSocket(); + CHECK(client != NULL); + bool ok = client->Connect(kLocalhost, port_str); + CHECK(ok); + + // Send headers which overflow the receive buffer. + static const int kBufferSize = 1000; + char buffer[kBufferSize]; + + // Long key and short value: XXXX....XXXX:0\r\n. + for (int i = 0; i < kBufferSize - 4; i++) { + buffer[i] = 'X'; + } + buffer[kBufferSize - 4] = ':'; + buffer[kBufferSize - 3] = '0'; + buffer[kBufferSize - 2] = '\r'; + buffer[kBufferSize - 1] = '\n'; + client->Send(buffer, kBufferSize); + + // Short key and long value: X:XXXX....XXXX\r\n. + buffer[0] = 'X'; + buffer[1] = ':'; + for (int i = 2; i < kBufferSize - 2; i++) { + buffer[i] = 'X'; + } + buffer[kBufferSize - 2] = '\r'; + buffer[kBufferSize - 1] = '\n'; + client->Send(buffer, kBufferSize); + + // Add empty body to request. + const char* content_length_zero_header = "Content-Length:0\r\n"; + client->Send(content_length_zero_header, strlen(content_length_zero_header)); + client->Send("\r\n", 2); + + // Wait until data is received. + server->Join(); + + // Check for empty body. + CHECK(server->body() == NULL); + + // Close the client before the server to avoid TIME_WAIT issues. + client->Shutdown(); + delete client; + delete server; +} + + +// Test for issue http://code.google.com/p/v8/issues/detail?id=289. +// Make sure that DebugGetLoadedScripts doesn't return scripts +// with disposed external source. +class EmptyExternalStringResource : public v8::String::ExternalStringResource { + public: + EmptyExternalStringResource() { empty_[0] = 0; } + virtual ~EmptyExternalStringResource() {} + virtual size_t length() const { return empty_.length(); } + virtual const uint16_t* data() const { return empty_.start(); } + private: + ::v8::internal::EmbeddedVector<uint16_t, 1> empty_; +}; + + +TEST(DebugGetLoadedScripts) { + v8::HandleScope scope; + DebugLocalContext env; + EmptyExternalStringResource source_ext_str; + v8::Local<v8::String> source = v8::String::NewExternal(&source_ext_str); + v8::Handle<v8::Script> evil_script = v8::Script::Compile(source); + Handle<i::ExternalTwoByteString> i_source( + i::ExternalTwoByteString::cast(*v8::Utils::OpenHandle(*source))); + // This situation can happen if source was an external string disposed + // by its owner. + i_source->set_resource(0); + + bool allow_natives_syntax = i::FLAG_allow_natives_syntax; + i::FLAG_allow_natives_syntax = true; + CompileRun( + "var scripts = %DebugGetLoadedScripts();" + "for (var i = 0; i < scripts.length; ++i) {" + " scripts[i].line_ends;" + "}"); + // Must not crash while accessing line_ends. + i::FLAG_allow_natives_syntax = allow_natives_syntax; +} diff --git a/v8/test/cctest/test-decls.cc b/v8/test/cctest/test-decls.cc new file mode 100644 index 0000000..e798fee --- /dev/null +++ b/v8/test/cctest/test-decls.cc @@ -0,0 +1,594 @@ +// Copyright 2007-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" + +#include "heap.h" +#include "cctest.h" + +using namespace v8; + + +enum Expectations { + EXPECT_RESULT, + EXPECT_EXCEPTION +}; + + +// A DeclarationContext holds a reference to a v8::Context and keeps +// track of various declaration related counters to make it easier to +// track if global declarations in the presence of interceptors behave +// the right way. +class DeclarationContext { + public: + DeclarationContext(); + + virtual ~DeclarationContext() { + if (is_initialized_) { + context_->Exit(); + context_.Dispose(); + } + } + + void Check(const char* source, + int get, int set, int has, + Expectations expectations, + v8::Handle<Value> value = Local<Value>()); + + int get_count() const { return get_count_; } + int set_count() const { return set_count_; } + int has_count() const { return has_count_; } + + protected: + virtual v8::Handle<Value> Get(Local<String> key); + virtual v8::Handle<Value> Set(Local<String> key, Local<Value> value); + virtual v8::Handle<Boolean> Has(Local<String> key); + + void InitializeIfNeeded(); + + // Get the holder for the interceptor. Default to the instance template + // but may be overwritten. + virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) { + return function->InstanceTemplate(); + } + + // The handlers are called as static functions that forward + // to the instance specific virtual methods. + static v8::Handle<Value> HandleGet(Local<String> key, + const AccessorInfo& info); + static v8::Handle<Value> HandleSet(Local<String> key, + Local<Value> value, + const AccessorInfo& info); + static v8::Handle<Boolean> HandleHas(Local<String> key, + const AccessorInfo& info); + + private: + bool is_initialized_; + Persistent<Context> context_; + Local<String> property_; + + int get_count_; + int set_count_; + int has_count_; + + static DeclarationContext* GetInstance(const AccessorInfo& info); +}; + + +DeclarationContext::DeclarationContext() + : is_initialized_(false), get_count_(0), set_count_(0), has_count_(0) { + // Do nothing. +} + + +void DeclarationContext::InitializeIfNeeded() { + if (is_initialized_) return; + HandleScope scope; + Local<FunctionTemplate> function = FunctionTemplate::New(); + Local<Value> data = Integer::New(reinterpret_cast<intptr_t>(this)); + GetHolder(function)->SetNamedPropertyHandler(&HandleGet, + &HandleSet, + &HandleHas, + 0, 0, + data); + context_ = Context::New(0, function->InstanceTemplate(), Local<Value>()); + context_->Enter(); + is_initialized_ = true; +} + + +void DeclarationContext::Check(const char* source, + int get, int set, int has, + Expectations expectations, + v8::Handle<Value> value) { + InitializeIfNeeded(); + // A retry after a GC may pollute the counts, so perform gc now + // to avoid that. + v8::internal::Heap::CollectGarbage(0, v8::internal::NEW_SPACE); + HandleScope scope; + TryCatch catcher; + catcher.SetVerbose(true); + Local<Value> result = Script::Compile(String::New(source))->Run(); + CHECK_EQ(get, get_count()); + CHECK_EQ(set, set_count()); + CHECK_EQ(has, has_count()); + if (expectations == EXPECT_RESULT) { + CHECK(!catcher.HasCaught()); + if (!value.IsEmpty()) { + CHECK_EQ(value, result); + } + } else { + CHECK(expectations == EXPECT_EXCEPTION); + CHECK(catcher.HasCaught()); + if (!value.IsEmpty()) { + CHECK_EQ(value, catcher.Exception()); + } + } +} + + +v8::Handle<Value> DeclarationContext::HandleGet(Local<String> key, + const AccessorInfo& info) { + DeclarationContext* context = GetInstance(info); + context->get_count_++; + return context->Get(key); +} + + +v8::Handle<Value> DeclarationContext::HandleSet(Local<String> key, + Local<Value> value, + const AccessorInfo& info) { + DeclarationContext* context = GetInstance(info); + context->set_count_++; + return context->Set(key, value); +} + + +v8::Handle<Boolean> DeclarationContext::HandleHas(Local<String> key, + const AccessorInfo& info) { + DeclarationContext* context = GetInstance(info); + context->has_count_++; + return context->Has(key); +} + + +DeclarationContext* DeclarationContext::GetInstance(const AccessorInfo& info) { + Local<Value> data = info.Data(); + return reinterpret_cast<DeclarationContext*>(Int32::Cast(*data)->Value()); +} + + +v8::Handle<Value> DeclarationContext::Get(Local<String> key) { + return v8::Handle<Value>(); +} + + +v8::Handle<Value> DeclarationContext::Set(Local<String> key, + Local<Value> value) { + return v8::Handle<Value>(); +} + + +v8::Handle<Boolean> DeclarationContext::Has(Local<String> key) { + return v8::Handle<Boolean>(); +} + + +// Test global declaration of a property the interceptor doesn't know +// about and doesn't handle. +TEST(Unknown) { + HandleScope scope; + + { DeclarationContext context; + context.Check("var x; x", + 1, // access + 1, // declaration + 2, // declaration + initialization + EXPECT_RESULT, Undefined()); + } + + { DeclarationContext context; + context.Check("var x = 0; x", + 1, // access + 2, // declaration + initialization + 2, // declaration + initialization + EXPECT_RESULT, Number::New(0)); + } + + { DeclarationContext context; + context.Check("function x() { }; x", + 1, // access + 1, // declaration + 0, + EXPECT_RESULT); + } + + { DeclarationContext context; + context.Check("const x; x", + 1, // access + 2, // declaration + initialization + 2, // declaration + initialization + EXPECT_RESULT, Undefined()); + } + + { DeclarationContext context; + context.Check("const x = 0; x", + 1, // access + 2, // declaration + initialization + 2, // declaration + initialization + EXPECT_RESULT, Undefined()); // SB 0 - BUG 1213579 + } +} + + + +class PresentPropertyContext: public DeclarationContext { + protected: + virtual v8::Handle<Boolean> Has(Local<String> key) { + return True(); + } +}; + + + +TEST(Present) { + HandleScope scope; + + { PresentPropertyContext context; + context.Check("var x; x", + 1, // access + 0, + 2, // declaration + initialization + EXPECT_EXCEPTION); // x is not defined! + } + + { PresentPropertyContext context; + context.Check("var x = 0; x", + 1, // access + 1, // initialization + 2, // declaration + initialization + EXPECT_RESULT, Number::New(0)); + } + + { PresentPropertyContext context; + context.Check("function x() { }; x", + 1, // access + 1, // declaration + 0, + EXPECT_RESULT); + } + + { PresentPropertyContext context; + context.Check("const x; x", + 0, + 0, + 1, // (re-)declaration + EXPECT_EXCEPTION); // x has already been declared! + } + + { PresentPropertyContext context; + context.Check("const x = 0; x", + 0, + 0, + 1, // (re-)declaration + EXPECT_EXCEPTION); // x has already been declared! + } +} + + + +class AbsentPropertyContext: public DeclarationContext { + protected: + virtual v8::Handle<Boolean> Has(Local<String> key) { + return False(); + } +}; + + +TEST(Absent) { + HandleScope scope; + + { AbsentPropertyContext context; + context.Check("var x; x", + 1, // access + 2, // declaration + initialization + 2, // declaration + initialization + EXPECT_RESULT, Undefined()); + } + + { AbsentPropertyContext context; + context.Check("var x = 0; x", + 1, // access + 2, // declaration + initialization + 2, // declaration + initialization + EXPECT_RESULT, Number::New(0)); + } + + { AbsentPropertyContext context; + context.Check("function x() { }; x", + 1, // access + 1, // declaration + 0, + EXPECT_RESULT); + } + + { AbsentPropertyContext context; + context.Check("const x; x", + 1, // access + 2, // declaration + initialization + 2, // declaration + initializetion + EXPECT_RESULT, Undefined()); + } + + { AbsentPropertyContext context; + context.Check("const x = 0; x", + 1, // access + 2, // declaration + initialization + 2, // declaration + initialization + EXPECT_RESULT, Undefined()); // SB 0 - BUG 1213579 + } + + { AbsentPropertyContext context; + context.Check("if (false) { var x = 0 }; x", + 1, // access + 1, // declaration + 1, // declaration + initialization + EXPECT_RESULT, Undefined()); + } +} + + + +class AppearingPropertyContext: public DeclarationContext { + public: + enum State { + DECLARE, + INITIALIZE_IF_ASSIGN, + UNKNOWN + }; + + AppearingPropertyContext() : state_(DECLARE) { } + + protected: + virtual v8::Handle<Boolean> Has(Local<String> key) { + switch (state_) { + case DECLARE: + // Force declaration by returning that the + // property is absent. + state_ = INITIALIZE_IF_ASSIGN; + return False(); + case INITIALIZE_IF_ASSIGN: + // Return that the property is present so we only get the + // setter called when initializing with a value. + state_ = UNKNOWN; + return True(); + default: + ASSERT(state_ == UNKNOWN); + break; + } + // Do the lookup in the object. + return v8::Local<Boolean>(); + } + + private: + State state_; +}; + + +TEST(Appearing) { + HandleScope scope; + + { AppearingPropertyContext context; + context.Check("var x; x", + 1, // access + 1, // declaration + 2, // declaration + initialization + EXPECT_RESULT, Undefined()); + } + + { AppearingPropertyContext context; + context.Check("var x = 0; x", + 1, // access + 2, // declaration + initialization + 2, // declaration + initialization + EXPECT_RESULT, Number::New(0)); + } + + { AppearingPropertyContext context; + context.Check("function x() { }; x", + 1, // access + 1, // declaration + 0, + EXPECT_RESULT); + } + + { AppearingPropertyContext context; + context.Check("const x; x", + 0, + 1, // declaration + 2, // declaration + initialization + EXPECT_EXCEPTION); // x has already been declared! + } + + { AppearingPropertyContext context; + context.Check("const x = 0; x", + 0, + 1, // declaration + 2, // declaration + initialization + EXPECT_EXCEPTION); // x has already been declared! + } +} + + + +class ReappearingPropertyContext: public DeclarationContext { + public: + enum State { + DECLARE, + DONT_DECLARE, + INITIALIZE, + UNKNOWN + }; + + ReappearingPropertyContext() : state_(DECLARE) { } + + protected: + virtual v8::Handle<Boolean> Has(Local<String> key) { + switch (state_) { + case DECLARE: + // Force the first declaration by returning that + // the property is absent. + state_ = DONT_DECLARE; + return False(); + case DONT_DECLARE: + // Ignore the second declaration by returning + // that the property is already there. + state_ = INITIALIZE; + return True(); + case INITIALIZE: + // Force an initialization by returning that + // the property is absent. This will make sure + // that the setter is called and it will not + // lead to redeclaration conflicts (yet). + state_ = UNKNOWN; + return False(); + default: + ASSERT(state_ == UNKNOWN); + break; + } + // Do the lookup in the object. + return v8::Local<Boolean>(); + } + + private: + State state_; +}; + + +TEST(Reappearing) { + HandleScope scope; + + { ReappearingPropertyContext context; + context.Check("const x; var x = 0", + 0, + 2, // var declaration + const initialization + 4, // 2 x declaration + 2 x initialization + EXPECT_EXCEPTION); // x has already been declared! + } +} + + + +class ExistsInPrototypeContext: public DeclarationContext { + protected: + virtual v8::Handle<Boolean> Has(Local<String> key) { + // Let it seem that the property exists in the prototype object. + return True(); + } + + // Use the prototype as the holder for the interceptors. + virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) { + return function->PrototypeTemplate(); + } +}; + + +TEST(ExistsInPrototype) { + HandleScope scope; + + // Sanity check to make sure that the holder of the interceptor + // really is the prototype object. + { ExistsInPrototypeContext context; + context.Check("this.x = 87; this.x", + 0, + 0, + 0, + EXPECT_RESULT, Number::New(87)); + } + + { ExistsInPrototypeContext context; + context.Check("var x; x", + 0, + 0, + 1, // declaration + EXPECT_RESULT, Undefined()); + } + + { ExistsInPrototypeContext context; + context.Check("var x = 0; x", + 0, + 0, + 1, // declaration + EXPECT_RESULT, Number::New(0)); + } + + { ExistsInPrototypeContext context; + context.Check("const x; x", + 0, + 0, + 1, // declaration + EXPECT_RESULT, Undefined()); + } + + { ExistsInPrototypeContext context; + context.Check("const x = 0; x", + 0, + 0, + 1, // declaration + EXPECT_RESULT, Number::New(0)); + } +} + + + +class AbsentInPrototypeContext: public DeclarationContext { + protected: + virtual v8::Handle<Boolean> Has(Local<String> key) { + // Let it seem that the property is absent in the prototype object. + return False(); + } + + // Use the prototype as the holder for the interceptors. + virtual Local<ObjectTemplate> GetHolder(Local<FunctionTemplate> function) { + return function->PrototypeTemplate(); + } +}; + + +TEST(AbsentInPrototype) { + HandleScope scope; + + { AbsentInPrototypeContext context; + context.Check("if (false) { var x = 0; }; x", + 0, + 0, + 1, // declaration + EXPECT_RESULT, Undefined()); + } +} diff --git a/v8/test/cctest/test-disasm-arm.cc b/v8/test/cctest/test-disasm-arm.cc new file mode 100644 index 0000000..1cca17d --- /dev/null +++ b/v8/test/cctest/test-disasm-arm.cc @@ -0,0 +1,281 @@ +// Copyright 2007-2008 the V8 project authors. 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. +// + +#include <stdlib.h> + +#include "v8.h" + +#include "debug.h" +#include "disasm.h" +#include "disassembler.h" +#include "macro-assembler.h" +#include "serialize.h" +#include "cctest.h" + +using namespace v8::internal; + + +static v8::Persistent<v8::Context> env; + +static void InitializeVM() { + if (env.IsEmpty()) { + env = v8::Context::New(); + } +} + + +bool DisassembleAndCompare(byte* pc, const char* compare_string) { + disasm::NameConverter converter; + disasm::Disassembler disasm(converter); + EmbeddedVector<char, 128> disasm_buffer; + + disasm.InstructionDecode(disasm_buffer, pc); + + if (strcmp(compare_string, disasm_buffer.start()) != 0) { + fprintf(stderr, + "expected: \n" + "%s\n" + "disassembled: \n" + "%s\n\n", + compare_string, disasm_buffer.start()); + return false; + } + return true; +} + + +// Setup V8 to a state where we can at least run the assembler and +// disassembler. Declare the variables and allocate the data structures used +// in the rest of the macros. +#define SETUP() \ + InitializeVM(); \ + v8::HandleScope scope; \ + byte *buffer = reinterpret_cast<byte*>(malloc(4*1024)); \ + Assembler assm(buffer, 4*1024); \ + bool failure = false; + + +// This macro assembles one instruction using the preallocated assembler and +// disassembles the generated instruction, comparing the output to the expected +// value. If the comparison fails an error message is printed, but the test +// continues to run until the end. +#define COMPARE(asm_, compare_string) \ + { \ + int pc_offset = assm.pc_offset(); \ + byte *pc = &buffer[pc_offset]; \ + assm.asm_; \ + if (!DisassembleAndCompare(pc, compare_string)) failure = true; \ + } + + +// Verify that all invocations of the COMPARE macro passed successfully. +// Exit with a failure if at least one of the tests failed. +#define VERIFY_RUN() \ +if (failure) { \ + V8_Fatal(__FILE__, __LINE__, "ARM Disassembler tests failed.\n"); \ + } + + +TEST(Type0) { + SETUP(); + + COMPARE(and_(r0, r1, Operand(r2)), + "e0010002 and r0, r1, r2"); + COMPARE(and_(r1, r2, Operand(r3), LeaveCC), + "e0021003 and r1, r2, r3"); + COMPARE(and_(r2, r3, Operand(r4), SetCC), + "e0132004 ands r2, r3, r4"); + COMPARE(and_(r3, r4, Operand(r5), LeaveCC, eq), + "00043005 andeq r3, r4, r5"); + + COMPARE(eor(r4, r5, Operand(r6, LSL, 0)), + "e0254006 eor r4, r5, r6"); + COMPARE(eor(r4, r5, Operand(r7, LSL, 1), SetCC), + "e0354087 eors r4, r5, r7, lsl #1"); + COMPARE(eor(r4, r5, Operand(r8, LSL, 2), LeaveCC, ne), + "10254108 eorne r4, r5, r8, lsl #2"); + COMPARE(eor(r4, r5, Operand(r9, LSL, 3), SetCC, cs), + "20354189 eorcss r4, r5, r9, lsl #3"); + + COMPARE(sub(r5, r6, Operand(r10, LSL, 31), LeaveCC, hs), + "20465f8a subcs r5, r6, sl, lsl #31"); + COMPARE(sub(r5, r6, Operand(r10, LSL, 30), SetCC, cc), + "30565f0a subccs r5, r6, sl, lsl #30"); + COMPARE(sub(r5, r6, Operand(r10, LSL, 24), LeaveCC, lo), + "30465c0a subcc r5, r6, sl, lsl #24"); + COMPARE(sub(r5, r6, Operand(r10, LSL, 16), SetCC, mi), + "4056580a submis r5, r6, sl, lsl #16"); + + COMPARE(rsb(r6, r7, Operand(fp)), + "e067600b rsb r6, r7, fp"); + COMPARE(rsb(r6, r7, Operand(fp, LSR, 1)), + "e06760ab rsb r6, r7, fp, lsr #1"); + COMPARE(rsb(r6, r7, Operand(fp, LSR, 0), SetCC), + "e077602b rsbs r6, r7, fp, lsr #32"); + COMPARE(rsb(r6, r7, Operand(fp, LSR, 31), LeaveCC, pl), + "50676fab rsbpl r6, r7, fp, lsr #31"); + + COMPARE(add(r7, r8, Operand(ip, ASR, 1)), + "e08870cc add r7, r8, ip, asr #1"); + COMPARE(add(r7, r8, Operand(ip, ASR, 0)), + "e088704c add r7, r8, ip, asr #32"); + COMPARE(add(r7, r8, Operand(ip), SetCC), + "e098700c adds r7, r8, ip"); + COMPARE(add(r7, r8, Operand(ip, ASR, 31), SetCC, vs), + "60987fcc addvss r7, r8, ip, asr #31"); + + COMPARE(adc(r7, fp, Operand(ip, ASR, 5)), + "e0ab72cc adc r7, fp, ip, asr #5"); + COMPARE(adc(r4, ip, Operand(ip, ASR, 1), LeaveCC, vc), + "70ac40cc adcvc r4, ip, ip, asr #1"); + COMPARE(adc(r5, sp, Operand(ip), SetCC), + "e0bd500c adcs r5, sp, ip"); + COMPARE(adc(r8, lr, Operand(ip, ASR, 31), SetCC, vc), + "70be8fcc adcvcs r8, lr, ip, asr #31"); + + COMPARE(sbc(r7, r1, Operand(ip, ROR, 1), LeaveCC, hi), + "80c170ec sbchi r7, r1, ip, ror #1"); + COMPARE(sbc(r7, r9, Operand(ip, ROR, 4)), + "e0c9726c sbc r7, r9, ip, ror #4"); + COMPARE(sbc(r7, r10, Operand(ip), SetCC), + "e0da700c sbcs r7, sl, ip"); + COMPARE(sbc(r7, ip, Operand(ip, ROR, 31), SetCC, hi), + "80dc7fec sbchis r7, ip, ip, ror #31"); + + COMPARE(rsc(r7, r8, Operand(ip, LSL, r0)), + "e0e8701c rsc r7, r8, ip, lsl r0"); + COMPARE(rsc(r7, r8, Operand(ip, LSL, r1)), + "e0e8711c rsc r7, r8, ip, lsl r1"); + COMPARE(rsc(r7, r8, Operand(ip), SetCC), + "e0f8700c rscs r7, r8, ip"); + COMPARE(rsc(r7, r8, Operand(ip, LSL, r3), SetCC, ls), + "90f8731c rsclss r7, r8, ip, lsl r3"); + + COMPARE(tst(r7, Operand(r5, ASR, ip), ge), + "a1170c55 tstge r7, r5, asr ip"); + COMPARE(tst(r7, Operand(r6, ASR, sp)), + "e1170d56 tst r7, r6, asr sp"); + COMPARE(tst(r7, Operand(r7), ge), + "a1170007 tstge r7, r7"); + COMPARE(tst(r7, Operand(r8, ASR, fp), ge), + "a1170b58 tstge r7, r8, asr fp"); + + COMPARE(teq(r7, Operand(r5, ROR, r0), lt), + "b1370075 teqlt r7, r5, ror r0"); + COMPARE(teq(r7, Operand(r6, ROR, lr)), + "e1370e76 teq r7, r6, ror lr"); + COMPARE(teq(r7, Operand(r7), lt), + "b1370007 teqlt r7, r7"); + COMPARE(teq(r7, Operand(r8, ROR, r1)), + "e1370178 teq r7, r8, ror r1"); + + COMPARE(cmp(r7, Operand(r4)), + "e1570004 cmp r7, r4"); + COMPARE(cmp(r7, Operand(r6, LSL, 1), gt), + "c1570086 cmpgt r7, r6, lsl #1"); + COMPARE(cmp(r7, Operand(r8, LSR, 3), gt), + "c15701a8 cmpgt r7, r8, lsr #3"); + COMPARE(cmp(r7, Operand(r8, ASR, 19)), + "e15709c8 cmp r7, r8, asr #19"); + + COMPARE(cmn(r0, Operand(r4)), + "e1700004 cmn r0, r4"); + COMPARE(cmn(r1, Operand(r6, ROR, 1)), + "e17100e6 cmn r1, r6, ror #1"); + COMPARE(cmn(r2, Operand(r8)), + "e1720008 cmn r2, r8"); + COMPARE(cmn(r3, Operand(fp), le), + "d173000b cmnle r3, fp"); + + COMPARE(orr(r7, r8, Operand(lr), LeaveCC, al), + "e188700e orr r7, r8, lr"); + COMPARE(orr(r7, r8, Operand(fp)), + "e188700b orr r7, r8, fp"); + COMPARE(orr(r7, r8, Operand(sp), SetCC), + "e198700d orrs r7, r8, sp"); + COMPARE(orr(r7, r8, Operand(ip), SetCC, al), + "e198700c orrs r7, r8, ip"); + + COMPARE(mov(r0, Operand(r1), LeaveCC, eq), + "01a00001 moveq r0, r1"); + COMPARE(mov(r0, Operand(r2)), + "e1a00002 mov r0, r2"); + COMPARE(mov(r0, Operand(r3), SetCC), + "e1b00003 movs r0, r3"); + COMPARE(mov(r0, Operand(r4), SetCC, pl), + "51b00004 movpls r0, r4"); + + COMPARE(bic(r0, lr, Operand(r1), LeaveCC, vs), + "61ce0001 bicvs r0, lr, r1"); + COMPARE(bic(r0, r9, Operand(r2), LeaveCC, vc), + "71c90002 bicvc r0, r9, r2"); + COMPARE(bic(r0, r5, Operand(r3), SetCC), + "e1d50003 bics r0, r5, r3"); + COMPARE(bic(r0, r1, Operand(r4), SetCC, pl), + "51d10004 bicpls r0, r1, r4"); + + COMPARE(mvn(r10, Operand(r1)), + "e1e0a001 mvn sl, r1"); + COMPARE(mvn(r9, Operand(r2)), + "e1e09002 mvn r9, r2"); + COMPARE(mvn(r0, Operand(r3), SetCC), + "e1f00003 mvns r0, r3"); + COMPARE(mvn(r5, Operand(r4), SetCC, cc), + "31f05004 mvnccs r5, r4"); + + VERIFY_RUN(); +} + + +TEST(Type1) { + SETUP(); + + COMPARE(and_(r0, r1, Operand(0x00000000)), + "e2010000 and r0, r1, #0"); + COMPARE(and_(r1, r2, Operand(0x00000001), LeaveCC), + "e2021001 and r1, r2, #1"); + COMPARE(and_(r2, r3, Operand(0x00000010), SetCC), + "e2132010 ands r2, r3, #16"); + COMPARE(and_(r3, r4, Operand(0x00000100), LeaveCC, eq), + "02043c01 andeq r3, r4, #256"); + COMPARE(and_(r4, r5, Operand(0x00001000), SetCC, ne), + "12154a01 andnes r4, r5, #4096"); + + COMPARE(eor(r4, r5, Operand(0x00001000)), + "e2254a01 eor r4, r5, #4096"); + COMPARE(eor(r4, r4, Operand(0x00010000), LeaveCC), + "e2244801 eor r4, r4, #65536"); + COMPARE(eor(r4, r3, Operand(0x00100000), SetCC), + "e2334601 eors r4, r3, #1048576"); + COMPARE(eor(r4, r2, Operand(0x01000000), LeaveCC, cs), + "22224401 eorcs r4, r2, #16777216"); + COMPARE(eor(r4, r1, Operand(0x10000000), SetCC, cc), + "32314201 eorccs r4, r1, #268435456"); + + VERIFY_RUN(); +} diff --git a/v8/test/cctest/test-disasm-ia32.cc b/v8/test/cctest/test-disasm-ia32.cc new file mode 100644 index 0000000..af9fb97 --- /dev/null +++ b/v8/test/cctest/test-disasm-ia32.cc @@ -0,0 +1,384 @@ +// Copyright 2007-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" + +#include "debug.h" +#include "disasm.h" +#include "disassembler.h" +#include "macro-assembler.h" +#include "serialize.h" +#include "cctest.h" + +using namespace v8::internal; + +static v8::Persistent<v8::Context> env; + +static void InitializeVM() { + if (env.IsEmpty()) { + env = v8::Context::New(); + } +} + + +#define __ assm. + + +static void DummyStaticFunction(Object* result) { +} + + +TEST(DisasmIa320) { + InitializeVM(); + v8::HandleScope scope; + v8::internal::byte buffer[1024]; + Assembler assm(buffer, sizeof buffer); + DummyStaticFunction(NULL); // just bloody use it (DELETE; debugging) + + // Short immediate instructions + __ adc(eax, 12345678); + __ add(Operand(eax), Immediate(12345678)); + __ or_(eax, 12345678); + __ sub(Operand(eax), Immediate(12345678)); + __ xor_(eax, 12345678); + __ and_(eax, 12345678); + Handle<FixedArray> foo = Factory::NewFixedArray(10, TENURED); + __ cmp(eax, foo); + + // ---- This one caused crash + __ mov(ebx, Operand(esp, ecx, times_2, 0)); // [esp+ecx*4] + + // ---- All instructions that I can think of + __ add(edx, Operand(ebx)); + __ add(edx, Operand(12, RelocInfo::NONE)); + __ add(edx, Operand(ebx, 0)); + __ add(edx, Operand(ebx, 16)); + __ add(edx, Operand(ebx, 1999)); + __ add(edx, Operand(esp, 0)); + __ add(edx, Operand(esp, 16)); + __ add(edx, Operand(esp, 1999)); + __ nop(); + __ add(edi, Operand(ebp, ecx, times_4, 0)); + __ add(edi, Operand(ebp, ecx, times_4, 12)); + __ add(Operand(ebp, ecx, times_4, 12), Immediate(12)); + + __ nop(); + __ add(Operand(ebx), Immediate(12)); + __ nop(); + __ adc(ecx, 12); + __ adc(ecx, 1000); + __ nop(); + __ and_(edx, 3); + __ and_(edx, Operand(esp, 4)); + __ cmp(edx, 3); + __ cmp(edx, Operand(esp, 4)); + __ cmp(Operand(ebp, ecx, times_4, 0), Immediate(1000)); + Handle<FixedArray> foo2 = Factory::NewFixedArray(10, TENURED); + __ cmp(ebx, foo2); + __ or_(edx, 3); + __ xor_(edx, 3); + __ nop(); + { + CHECK(CpuFeatures::IsSupported(CpuFeatures::CPUID)); + CpuFeatures::Scope fscope(CpuFeatures::CPUID); + __ cpuid(); + } + { + CHECK(CpuFeatures::IsSupported(CpuFeatures::RDTSC)); + CpuFeatures::Scope fscope(CpuFeatures::RDTSC); + __ rdtsc(); + } + __ movsx_b(edx, Operand(ecx)); + __ movsx_w(edx, Operand(ecx)); + __ movzx_b(edx, Operand(ecx)); + __ movzx_w(edx, Operand(ecx)); + + __ nop(); + __ imul(edx, Operand(ecx)); + __ shld(edx, Operand(ecx)); + __ shrd(edx, Operand(ecx)); + __ bts(Operand(edx), ecx); + __ bts(Operand(ebx, ecx, times_4, 0), ecx); + __ nop(); + __ pushad(); + __ popad(); + __ pushfd(); + __ popfd(); + __ push(Immediate(12)); + __ push(Immediate(23456)); + __ push(ecx); + __ push(esi); + __ push(Operand(ebp, JavaScriptFrameConstants::kFunctionOffset)); + __ push(Operand(ebx, ecx, times_4, 0)); + __ push(Operand(ebx, ecx, times_4, 0)); + __ push(Operand(ebx, ecx, times_4, 10000)); + __ pop(edx); + __ pop(eax); + __ pop(Operand(ebx, ecx, times_4, 0)); + __ nop(); + + __ add(edx, Operand(esp, 16)); + __ add(edx, Operand(ecx)); + __ mov_b(edx, Operand(ecx)); + __ mov_b(Operand(ecx), 6); + __ mov_b(Operand(ebx, ecx, times_4, 10000), 6); + __ mov_b(Operand(esp, 16), edx); + __ mov_w(edx, Operand(esp, 16)); + __ mov_w(Operand(esp, 16), edx); + __ nop(); + __ movsx_w(edx, Operand(esp, 12)); + __ movsx_b(edx, Operand(esp, 12)); + __ movzx_w(edx, Operand(esp, 12)); + __ movzx_b(edx, Operand(esp, 12)); + __ nop(); + __ mov(edx, 1234567); + __ mov(edx, Operand(esp, 12)); + __ mov(Operand(ebx, ecx, times_4, 10000), Immediate(12345)); + __ mov(Operand(ebx, ecx, times_4, 10000), edx); + __ nop(); + __ dec_b(edx); + __ dec(edx); + __ cdq(); + + __ nop(); + __ idiv(edx); + __ mul(edx); + __ neg(edx); + __ not_(edx); + __ test(Operand(ebx, ecx, times_4, 10000), Immediate(123456)); + + __ imul(edx, Operand(ebx, ecx, times_4, 10000)); + __ imul(edx, ecx, 12); + __ imul(edx, ecx, 1000); + + __ inc(edx); + __ inc(Operand(ebx, ecx, times_4, 10000)); + __ push(Operand(ebx, ecx, times_4, 10000)); + __ pop(Operand(ebx, ecx, times_4, 10000)); + __ call(Operand(ebx, ecx, times_4, 10000)); + __ jmp(Operand(ebx, ecx, times_4, 10000)); + + __ lea(edx, Operand(ebx, ecx, times_4, 10000)); + __ or_(edx, 12345); + __ or_(edx, Operand(ebx, ecx, times_4, 10000)); + + __ nop(); + + __ rcl(edx, 1); + __ rcl(edx, 7); + __ sar(edx, 1); + __ sar(edx, 6); + __ sar(edx); + __ sbb(edx, Operand(ebx, ecx, times_4, 10000)); + __ shld(edx, Operand(ebx, ecx, times_4, 10000)); + __ shl(edx, 1); + __ shl(edx, 6); + __ shl(edx); + __ shrd(edx, Operand(ebx, ecx, times_4, 10000)); + __ shr(edx, 7); + __ shr(edx); + + + // Immediates + + __ adc(edx, 12345); + + __ add(Operand(ebx), Immediate(12)); + __ add(Operand(edx, ecx, times_4, 10000), Immediate(12)); + + __ and_(ebx, 12345); + + __ cmp(ebx, 12345); + __ cmp(Operand(ebx), Immediate(12)); + __ cmp(Operand(edx, ecx, times_4, 10000), Immediate(12)); + + __ or_(ebx, 12345); + + __ sub(Operand(ebx), Immediate(12)); + __ sub(Operand(edx, ecx, times_4, 10000), Immediate(12)); + + __ xor_(ebx, 12345); + + __ imul(edx, ecx, 12); + __ imul(edx, ecx, 1000); + + + + __ sub(edx, Operand(ebx, ecx, times_4, 10000)); + __ sub(edx, Operand(ebx)); + + __ test(edx, Immediate(12345)); + __ test(edx, Operand(ebx, ecx, times_8, 10000)); + __ nop(); + + __ xor_(edx, 12345); + __ xor_(edx, Operand(ebx, ecx, times_8, 10000)); + __ bts(Operand(ebx, ecx, times_8, 10000), edx); + __ hlt(); + __ int3(); + __ ret(0); + __ ret(8); + + // Calls + + Label L1, L2; + __ bind(&L1); + __ nop(); + __ call(&L1); + __ call(&L2); + __ nop(); + __ bind(&L2); + __ call(Operand(ebx, ecx, times_4, 10000)); + __ nop(); + Handle<Code> ic(Builtins::builtin(Builtins::LoadIC_Initialize)); + __ call(ic, RelocInfo::CODE_TARGET); + __ nop(); + __ call(FUNCTION_ADDR(DummyStaticFunction), RelocInfo::RUNTIME_ENTRY); + __ nop(); + + __ jmp(&L1); + __ jmp(Operand(ebx, ecx, times_4, 10000)); + ExternalReference after_break_target = + ExternalReference(Debug_Address::AfterBreakTarget()); + __ jmp(Operand::StaticVariable(after_break_target)); + __ jmp(ic, RelocInfo::CODE_TARGET); + __ nop(); + + + Label Ljcc; + __ nop(); + // long jumps + __ j(overflow, &Ljcc); + __ j(no_overflow, &Ljcc); + __ j(below, &Ljcc); + __ j(above_equal, &Ljcc); + __ j(equal, &Ljcc); + __ j(not_equal, &Ljcc); + __ j(below_equal, &Ljcc); + __ j(above, &Ljcc); + __ j(sign, &Ljcc); + __ j(not_sign, &Ljcc); + __ j(parity_even, &Ljcc); + __ j(parity_odd, &Ljcc); + __ j(less, &Ljcc); + __ j(greater_equal, &Ljcc); + __ j(less_equal, &Ljcc); + __ j(greater, &Ljcc); + __ nop(); + __ bind(&Ljcc); + // short jumps + __ j(overflow, &Ljcc); + __ j(no_overflow, &Ljcc); + __ j(below, &Ljcc); + __ j(above_equal, &Ljcc); + __ j(equal, &Ljcc); + __ j(not_equal, &Ljcc); + __ j(below_equal, &Ljcc); + __ j(above, &Ljcc); + __ j(sign, &Ljcc); + __ j(not_sign, &Ljcc); + __ j(parity_even, &Ljcc); + __ j(parity_odd, &Ljcc); + __ j(less, &Ljcc); + __ j(greater_equal, &Ljcc); + __ j(less_equal, &Ljcc); + __ j(greater, &Ljcc); + + // checking hints + __ j(zero, &Ljcc, taken); + __ j(zero, &Ljcc, not_taken); + + // __ mov(Operand::StaticVariable(Top::handler_address()), eax); + // 0xD9 instructions + __ nop(); + + __ fld1(); + __ fldz(); + __ fabs(); + __ fchs(); + __ fprem(); + __ fprem1(); + __ fincstp(); + __ ftst(); + __ fxch(3); + __ fld_s(Operand(ebx, ecx, times_4, 10000)); + __ fstp_s(Operand(ebx, ecx, times_4, 10000)); + __ ffree(3); + __ fld_d(Operand(ebx, ecx, times_4, 10000)); + __ fstp_d(Operand(ebx, ecx, times_4, 10000)); + __ nop(); + + __ fild_s(Operand(ebx, ecx, times_4, 10000)); + __ fistp_s(Operand(ebx, ecx, times_4, 10000)); + __ fild_d(Operand(ebx, ecx, times_4, 10000)); + __ fistp_d(Operand(ebx, ecx, times_4, 10000)); + __ fnstsw_ax(); + __ nop(); + __ fadd(3); + __ fsub(3); + __ fmul(3); + __ fdiv(3); + + __ faddp(3); + __ fsubp(3); + __ fmulp(3); + __ fdivp(3); + __ fcompp(); + __ fwait(); + __ nop(); + { + CHECK(CpuFeatures::IsSupported(CpuFeatures::SSE2)); + CpuFeatures::Scope fscope(CpuFeatures::SSE2); + __ cvttss2si(edx, Operand(ebx, ecx, times_4, 10000)); + __ cvtsi2sd(xmm1, Operand(ebx, ecx, times_4, 10000)); + __ addsd(xmm1, xmm0); + __ mulsd(xmm1, xmm0); + __ subsd(xmm1, xmm0); + __ divsd(xmm1, xmm0); + __ movdbl(xmm1, Operand(ebx, ecx, times_4, 10000)); + __ movdbl(Operand(ebx, ecx, times_4, 10000), xmm1); + } + __ ret(0); + + CodeDesc desc; + assm.GetCode(&desc); + Object* code = Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value())); + CHECK(code->IsCode()); +#ifdef DEBUG + Code::cast(code)->Print(); + byte* begin = Code::cast(code)->instruction_start(); + byte* end = begin + Code::cast(code)->instruction_size(); + disasm::Disassembler::Disassemble(stdout, begin, end); +#endif +} + +#undef __ diff --git a/v8/test/cctest/test-flags.cc b/v8/test/cctest/test-flags.cc new file mode 100644 index 0000000..9019a89 --- /dev/null +++ b/v8/test/cctest/test-flags.cc @@ -0,0 +1,234 @@ +// Copyright 2006-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" +#include "cctest.h" + +using namespace v8::internal; + +// This test must be executed first! +TEST(Default) { + CHECK(FLAG_testing_bool_flag); + CHECK_EQ(13, FLAG_testing_int_flag); + CHECK_EQ(2.5, FLAG_testing_float_flag); + CHECK_EQ(0, strcmp(FLAG_testing_string_flag, "Hello, world!")); +} + + +static void SetFlagsToDefault() { + FlagList::ResetAllFlags(); + TestDefault(); +} + + +TEST(Flags1) { + FlagList::PrintHelp(); +} + + +TEST(Flags2) { + SetFlagsToDefault(); + int argc = 7; + const char* argv[] = { "Test2", "-notesting-bool-flag", "notaflag", + "--testing_int_flag=77", "-testing_float_flag=.25", + "--testing_string_flag", "no way!" }; + CHECK_EQ(0, FlagList::SetFlagsFromCommandLine(&argc, + const_cast<char **>(argv), + false)); + CHECK_EQ(7, argc); + CHECK(!FLAG_testing_bool_flag); + CHECK_EQ(77, FLAG_testing_int_flag); + CHECK_EQ(.25, FLAG_testing_float_flag); + CHECK_EQ(0, strcmp(FLAG_testing_string_flag, "no way!")); +} + + +TEST(Flags2b) { + SetFlagsToDefault(); + const char* str = + " -notesting-bool-flag notaflag --testing_int_flag=77 " + "-testing_float_flag=.25 " + "--testing_string_flag no_way! "; + CHECK_EQ(0, FlagList::SetFlagsFromString(str, strlen(str))); + CHECK(!FLAG_testing_bool_flag); + CHECK_EQ(77, FLAG_testing_int_flag); + CHECK_EQ(.25, FLAG_testing_float_flag); + CHECK_EQ(0, strcmp(FLAG_testing_string_flag, "no_way!")); +} + + +TEST(Flags3) { + SetFlagsToDefault(); + int argc = 8; + const char* argv[] = + { "Test3", "--testing_bool_flag", "notaflag", + "--testing_int_flag", "-666", + "--testing_float_flag", "-12E10", "-testing-string-flag=foo-bar" }; + CHECK_EQ(0, FlagList::SetFlagsFromCommandLine(&argc, + const_cast<char **>(argv), + true)); + CHECK_EQ(2, argc); + CHECK(FLAG_testing_bool_flag); + CHECK_EQ(-666, FLAG_testing_int_flag); + CHECK_EQ(-12E10, FLAG_testing_float_flag); + CHECK_EQ(0, strcmp(FLAG_testing_string_flag, "foo-bar")); +} + + +TEST(Flags3b) { + SetFlagsToDefault(); + const char* str = + "--testing_bool_flag notaflag --testing_int_flag -666 " + "--testing_float_flag -12E10 " + "-testing-string-flag=foo-bar"; + CHECK_EQ(0, FlagList::SetFlagsFromString(str, strlen(str))); + CHECK(FLAG_testing_bool_flag); + CHECK_EQ(-666, FLAG_testing_int_flag); + CHECK_EQ(-12E10, FLAG_testing_float_flag); + CHECK_EQ(0, strcmp(FLAG_testing_string_flag, "foo-bar")); +} + + +TEST(Flags4) { + SetFlagsToDefault(); + int argc = 3; + const char* argv[] = { "Test4", "--testing_bool_flag", "--foo" }; + CHECK_EQ(0, FlagList::SetFlagsFromCommandLine(&argc, + const_cast<char **>(argv), + true)); + CHECK_EQ(2, argc); +} + + +TEST(Flags4b) { + SetFlagsToDefault(); + const char* str = "--testing_bool_flag --foo"; + CHECK_EQ(2, FlagList::SetFlagsFromString(str, strlen(str))); +} + + +TEST(Flags5) { + SetFlagsToDefault(); + int argc = 2; + const char* argv[] = { "Test5", "--testing_int_flag=\"foobar\"" }; + CHECK_EQ(1, FlagList::SetFlagsFromCommandLine(&argc, + const_cast<char **>(argv), + true)); + CHECK_EQ(2, argc); +} + + +TEST(Flags5b) { + SetFlagsToDefault(); + const char* str = " --testing_int_flag=\"foobar\""; + CHECK_EQ(1, FlagList::SetFlagsFromString(str, strlen(str))); +} + + +TEST(Flags6) { + SetFlagsToDefault(); + int argc = 4; + const char* argv[] = { "Test5", "--testing-int-flag", "0", + "--testing_float_flag" }; + CHECK_EQ(3, FlagList::SetFlagsFromCommandLine(&argc, + const_cast<char **>(argv), + true)); + CHECK_EQ(4, argc); +} + + +TEST(Flags6b) { + SetFlagsToDefault(); + const char* str = " --testing-int-flag 0 --testing_float_flag "; + CHECK_EQ(3, FlagList::SetFlagsFromString(str, strlen(str))); +} + + +TEST(FlagsJSArguments1) { + SetFlagsToDefault(); + int argc = 6; + const char* argv[] = {"TestJSArgs1", + "--testing-int-flag", "42", + "--", "testing-float-flag", "7"}; + CHECK_EQ(0, FlagList::SetFlagsFromCommandLine(&argc, + const_cast<char **>(argv), + true)); + CHECK_EQ(42, FLAG_testing_int_flag); + CHECK_EQ(2.5, FLAG_testing_float_flag); + CHECK_EQ(2, FLAG_js_arguments.argc()); + CHECK_EQ(0, strcmp(FLAG_js_arguments[0], "testing-float-flag")); + CHECK_EQ(0, strcmp(FLAG_js_arguments[1], "7")); + CHECK_EQ(1, argc); +} + + +TEST(FlagsJSArguments1b) { + SetFlagsToDefault(); + const char* str = "--testing-int-flag 42 -- testing-float-flag 7"; + CHECK_EQ(0, FlagList::SetFlagsFromString(str, strlen(str))); + CHECK_EQ(42, FLAG_testing_int_flag); + CHECK_EQ(2.5, FLAG_testing_float_flag); + CHECK_EQ(2, FLAG_js_arguments.argc()); + CHECK_EQ(0, strcmp(FLAG_js_arguments[0], "testing-float-flag")); + CHECK_EQ(0, strcmp(FLAG_js_arguments[1], "7")); +} + + +TEST(FlagsJSArguments2) { + SetFlagsToDefault(); + const char* str = "--testing-int-flag 42 --js-arguments testing-float-flag 7"; + CHECK_EQ(0, FlagList::SetFlagsFromString(str, strlen(str))); + CHECK_EQ(42, FLAG_testing_int_flag); + CHECK_EQ(2.5, FLAG_testing_float_flag); + CHECK_EQ(2, FLAG_js_arguments.argc()); + CHECK_EQ(0, strcmp(FLAG_js_arguments[0], "testing-float-flag")); + CHECK_EQ(0, strcmp(FLAG_js_arguments[1], "7")); +} + + +TEST(FlagsJSArguments3) { + SetFlagsToDefault(); + const char* str = "--testing-int-flag 42 --js-arguments=testing-float-flag 7"; + CHECK_EQ(0, FlagList::SetFlagsFromString(str, strlen(str))); + CHECK_EQ(42, FLAG_testing_int_flag); + CHECK_EQ(2.5, FLAG_testing_float_flag); + CHECK_EQ(2, FLAG_js_arguments.argc()); + CHECK_EQ(0, strcmp(FLAG_js_arguments[0], "testing-float-flag")); + CHECK_EQ(0, strcmp(FLAG_js_arguments[1], "7")); +} + + +TEST(FlagsJSArguments4) { + SetFlagsToDefault(); + const char* str = "--testing-int-flag 42 --"; + CHECK_EQ(0, FlagList::SetFlagsFromString(str, strlen(str))); + CHECK_EQ(42, FLAG_testing_int_flag); + CHECK_EQ(0, FLAG_js_arguments.argc()); +} + diff --git a/v8/test/cctest/test-hashmap.cc b/v8/test/cctest/test-hashmap.cc new file mode 100644 index 0000000..4918d5d --- /dev/null +++ b/v8/test/cctest/test-hashmap.cc @@ -0,0 +1,123 @@ +// Copyright 2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" +#include "hashmap.h" +#include "cctest.h" + +using namespace v8::internal; + +static bool DefaultMatchFun(void* a, void* b) { + return a == b; +} + + +class IntSet { + public: + IntSet() : map_(DefaultMatchFun) {} + + void Insert(int x) { + ASSERT(x != 0); // 0 corresponds to (void*)NULL - illegal key value + HashMap::Entry* p = map_.Lookup(reinterpret_cast<void*>(x), Hash(x), true); + CHECK(p != NULL); // insert is set! + CHECK_EQ(reinterpret_cast<void*>(x), p->key); + // we don't care about p->value + } + + bool Present(int x) { + HashMap::Entry* p = map_.Lookup(reinterpret_cast<void*>(x), Hash(x), false); + if (p != NULL) { + CHECK_EQ(reinterpret_cast<void*>(x), p->key); + } + return p != NULL; + } + + void Clear() { + map_.Clear(); + } + + uint32_t occupancy() const { + uint32_t count = 0; + for (HashMap::Entry* p = map_.Start(); p != NULL; p = map_.Next(p)) { + count++; + } + CHECK_EQ(map_.occupancy(), static_cast<double>(count)); + return count; + } + + private: + HashMap map_; + static uint32_t Hash(uint32_t key) { return key * 23; } +}; + + +TEST(Set) { + IntSet set; + CHECK_EQ(0, set.occupancy()); + + set.Insert(1); + set.Insert(2); + set.Insert(3); + CHECK_EQ(3, set.occupancy()); + + set.Insert(2); + set.Insert(3); + CHECK_EQ(3, set.occupancy()); + + CHECK(set.Present(1)); + CHECK(set.Present(2)); + CHECK(set.Present(3)); + CHECK(!set.Present(4)); + CHECK_EQ(3, set.occupancy()); + + set.Clear(); + CHECK_EQ(0, set.occupancy()); + + // Insert a long series of values. + const int start = 453; + const int factor = 13; + const int offset = 7; + const uint32_t n = 1000; + + int x = start; + for (uint32_t i = 0; i < n; i++) { + CHECK_EQ(i, static_cast<double>(set.occupancy())); + set.Insert(x); + x = x*factor + offset; + } + + // Verify the same sequence of values. + x = start; + for (uint32_t i = 0; i < n; i++) { + CHECK(set.Present(x)); + x = x*factor + offset; + } + + CHECK_EQ(n, static_cast<double>(set.occupancy())); +} diff --git a/v8/test/cctest/test-heap.cc b/v8/test/cctest/test-heap.cc new file mode 100644 index 0000000..e35ac5f --- /dev/null +++ b/v8/test/cctest/test-heap.cc @@ -0,0 +1,785 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. + +#include <stdlib.h> + +#include "v8.h" + +#include "execution.h" +#include "factory.h" +#include "macro-assembler.h" +#include "global-handles.h" +#include "cctest.h" + +using namespace v8::internal; + +static v8::Persistent<v8::Context> env; + +static void InitializeVM() { + if (env.IsEmpty()) env = v8::Context::New(); + v8::HandleScope scope; + env->Enter(); +} + + +static void CheckMap(Map* map, int type, int instance_size) { + CHECK(map->IsHeapObject()); +#ifdef DEBUG + CHECK(Heap::Contains(map)); +#endif + CHECK_EQ(Heap::meta_map(), map->map()); + CHECK_EQ(type, map->instance_type()); + CHECK_EQ(instance_size, map->instance_size()); +} + + +TEST(HeapMaps) { + InitializeVM(); + CheckMap(Heap::meta_map(), MAP_TYPE, Map::kSize); + CheckMap(Heap::heap_number_map(), HEAP_NUMBER_TYPE, HeapNumber::kSize); + CheckMap(Heap::fixed_array_map(), FIXED_ARRAY_TYPE, Array::kHeaderSize); + CheckMap(Heap::long_string_map(), LONG_STRING_TYPE, + SeqTwoByteString::kHeaderSize); +} + + +static void CheckOddball(Object* obj, const char* string) { + CHECK(obj->IsOddball()); + bool exc; + Object* print_string = *Execution::ToString(Handle<Object>(obj), &exc); + CHECK(String::cast(print_string)->IsEqualTo(CStrVector(string))); +} + + +static void CheckSmi(int value, const char* string) { + bool exc; + Object* print_string = + *Execution::ToString(Handle<Object>(Smi::FromInt(value)), &exc); + CHECK(String::cast(print_string)->IsEqualTo(CStrVector(string))); +} + + +static void CheckNumber(double value, const char* string) { + Object* obj = Heap::NumberFromDouble(value); + CHECK(obj->IsNumber()); + bool exc; + Object* print_string = *Execution::ToString(Handle<Object>(obj), &exc); + CHECK(String::cast(print_string)->IsEqualTo(CStrVector(string))); +} + + +static void CheckFindCodeObject() { + // Test FindCodeObject +#define __ assm. + + Assembler assm(NULL, 0); + + __ nop(); // supported on all architectures + + CodeDesc desc; + assm.GetCode(&desc); + Object* code = Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value())); + CHECK(code->IsCode()); + + HeapObject* obj = HeapObject::cast(code); + Address obj_addr = obj->address(); + + for (int i = 0; i < obj->Size(); i += kPointerSize) { + Object* found = Heap::FindCodeObject(obj_addr + i); + CHECK_EQ(code, found); + } + + Object* copy = Heap::CreateCode(desc, + NULL, + Code::ComputeFlags(Code::STUB), + Handle<Object>(Heap::undefined_value())); + CHECK(copy->IsCode()); + HeapObject* obj_copy = HeapObject::cast(copy); + Object* not_right = Heap::FindCodeObject(obj_copy->address() + + obj_copy->Size() / 2); + CHECK(not_right != code); +} + + +TEST(HeapObjects) { + InitializeVM(); + + v8::HandleScope sc; + Object* value = Heap::NumberFromDouble(1.000123); + CHECK(value->IsHeapNumber()); + CHECK(value->IsNumber()); + CHECK_EQ(1.000123, value->Number()); + + value = Heap::NumberFromDouble(1.0); + CHECK(value->IsSmi()); + CHECK(value->IsNumber()); + CHECK_EQ(1.0, value->Number()); + + value = Heap::NumberFromInt32(1024); + CHECK(value->IsSmi()); + CHECK(value->IsNumber()); + CHECK_EQ(1024.0, value->Number()); + + value = Heap::NumberFromInt32(Smi::kMinValue); + CHECK(value->IsSmi()); + CHECK(value->IsNumber()); + CHECK_EQ(Smi::kMinValue, Smi::cast(value)->value()); + + value = Heap::NumberFromInt32(Smi::kMaxValue); + CHECK(value->IsSmi()); + CHECK(value->IsNumber()); + CHECK_EQ(Smi::kMaxValue, Smi::cast(value)->value()); + + value = Heap::NumberFromInt32(Smi::kMinValue - 1); + CHECK(value->IsHeapNumber()); + CHECK(value->IsNumber()); + CHECK_EQ(static_cast<double>(Smi::kMinValue - 1), value->Number()); + + value = Heap::NumberFromInt32(Smi::kMaxValue + 1); + CHECK(value->IsHeapNumber()); + CHECK(value->IsNumber()); + CHECK_EQ(static_cast<double>(Smi::kMaxValue + 1), value->Number()); + + // nan oddball checks + CHECK(Heap::nan_value()->IsNumber()); + CHECK(isnan(Heap::nan_value()->Number())); + + Object* str = Heap::AllocateStringFromAscii(CStrVector("fisk hest ")); + if (!str->IsFailure()) { + String* s = String::cast(str); + CHECK(s->IsString()); + CHECK_EQ(10, s->length()); + } else { + CHECK(false); + } + + String* object_symbol = String::cast(Heap::Object_symbol()); + CHECK(Top::context()->global()->HasLocalProperty(object_symbol)); + + // Check ToString for oddballs + CheckOddball(Heap::true_value(), "true"); + CheckOddball(Heap::false_value(), "false"); + CheckOddball(Heap::null_value(), "null"); + CheckOddball(Heap::undefined_value(), "undefined"); + + // Check ToString for Smis + CheckSmi(0, "0"); + CheckSmi(42, "42"); + CheckSmi(-42, "-42"); + + // Check ToString for Numbers + CheckNumber(1.1, "1.1"); + + CheckFindCodeObject(); +} + + +TEST(Tagging) { + InitializeVM(); + CHECK(Smi::FromInt(42)->IsSmi()); + CHECK(Failure::RetryAfterGC(12, NEW_SPACE)->IsFailure()); + CHECK_EQ(12, Failure::RetryAfterGC(12, NEW_SPACE)->requested()); + CHECK_EQ(NEW_SPACE, Failure::RetryAfterGC(12, NEW_SPACE)->allocation_space()); + CHECK_EQ(OLD_POINTER_SPACE, + Failure::RetryAfterGC(12, OLD_POINTER_SPACE)->allocation_space()); + CHECK(Failure::Exception()->IsFailure()); + CHECK(Smi::FromInt(Smi::kMinValue)->IsSmi()); + CHECK(Smi::FromInt(Smi::kMaxValue)->IsSmi()); +} + + +TEST(GarbageCollection) { + InitializeVM(); + + v8::HandleScope sc; + // check GC when heap is empty + int free_bytes = Heap::MaxHeapObjectSize(); + CHECK(Heap::CollectGarbage(free_bytes, NEW_SPACE)); + + // allocate a function and keep it in global object's property + String* func_name = String::cast(Heap::LookupAsciiSymbol("theFunction")); + SharedFunctionInfo* function_share = + SharedFunctionInfo::cast(Heap::AllocateSharedFunctionInfo(func_name)); + JSFunction* function = + JSFunction::cast(Heap::AllocateFunction(*Top::function_map(), + function_share, + Heap::undefined_value())); + Map* initial_map = + Map::cast(Heap::AllocateMap(JS_OBJECT_TYPE, JSObject::kHeaderSize)); + function->set_initial_map(initial_map); + Top::context()->global()->SetProperty(func_name, function, NONE); + + // allocate an object, but it is unrooted + String* prop_name = String::cast(Heap::LookupAsciiSymbol("theSlot")); + String* prop_namex = String::cast(Heap::LookupAsciiSymbol("theSlotx")); + JSObject* obj = JSObject::cast(Heap::AllocateJSObject(function)); + obj->SetProperty(prop_name, Smi::FromInt(23), NONE); + obj->SetProperty(prop_namex, Smi::FromInt(24), NONE); + + CHECK_EQ(Smi::FromInt(23), obj->GetProperty(prop_name)); + CHECK_EQ(Smi::FromInt(24), obj->GetProperty(prop_namex)); + + CHECK(Heap::CollectGarbage(free_bytes, NEW_SPACE)); + + // function should be alive, func_name might be invalid after GC + func_name = String::cast(Heap::LookupAsciiSymbol("theFunction")); + CHECK(Top::context()->global()->HasLocalProperty(func_name)); + // check function is retained + Object* func_value = Top::context()->global()->GetProperty(func_name); + CHECK(func_value->IsJSFunction()); + // old function pointer may not be valid + function = JSFunction::cast(func_value); + + // allocate another object, make it reachable from global + obj = JSObject::cast(Heap::AllocateJSObject(function)); + String* obj_name = String::cast(Heap::LookupAsciiSymbol("theObject")); + Top::context()->global()->SetProperty(obj_name, obj, NONE); + // set property + prop_name = String::cast(Heap::LookupAsciiSymbol("theSlot")); + obj->SetProperty(prop_name, Smi::FromInt(23), NONE); + + // after gc, it should survive + CHECK(Heap::CollectGarbage(free_bytes, NEW_SPACE)); + + obj_name = String::cast(Heap::LookupAsciiSymbol("theObject")); + CHECK(Top::context()->global()->HasLocalProperty(obj_name)); + CHECK(Top::context()->global()->GetProperty(obj_name)->IsJSObject()); + obj = JSObject::cast(Top::context()->global()->GetProperty(obj_name)); + prop_name = String::cast(Heap::LookupAsciiSymbol("theSlot")); + CHECK_EQ(Smi::FromInt(23), obj->GetProperty(prop_name)); +} + + +static void VerifyStringAllocation(const char* string) { + String* s = String::cast(Heap::AllocateStringFromUtf8(CStrVector(string))); + CHECK_EQ(static_cast<int>(strlen(string)), s->length()); + for (int index = 0; index < s->length(); index++) { + CHECK_EQ(static_cast<uint16_t>(string[index]), s->Get(index)); } +} + + +TEST(String) { + InitializeVM(); + + VerifyStringAllocation("a"); + VerifyStringAllocation("ab"); + VerifyStringAllocation("abc"); + VerifyStringAllocation("abcd"); + VerifyStringAllocation("fiskerdrengen er paa havet"); +} + + +TEST(LocalHandles) { + InitializeVM(); + + v8::HandleScope scope; + const char* name = "Kasper the spunky"; + Handle<String> string = Factory::NewStringFromAscii(CStrVector(name)); + CHECK_EQ(static_cast<int>(strlen(name)), string->length()); +} + + +TEST(GlobalHandles) { + InitializeVM(); + + Object* i = Heap::AllocateStringFromAscii(CStrVector("fisk")); + Object* u = Heap::AllocateHeapNumber(1.12344); + + Handle<Object> h1 = GlobalHandles::Create(i); + Handle<Object> h2 = GlobalHandles::Create(u); + Handle<Object> h3 = GlobalHandles::Create(i); + Handle<Object> h4 = GlobalHandles::Create(u); + + // after gc, it should survive + CHECK(Heap::CollectGarbage(0, NEW_SPACE)); + + CHECK((*h1)->IsString()); + CHECK((*h2)->IsHeapNumber()); + CHECK((*h3)->IsString()); + CHECK((*h4)->IsHeapNumber()); + + CHECK_EQ(*h3, *h1); + GlobalHandles::Destroy(h1.location()); + GlobalHandles::Destroy(h3.location()); + + CHECK_EQ(*h4, *h2); + GlobalHandles::Destroy(h2.location()); + GlobalHandles::Destroy(h4.location()); +} + + +static bool WeakPointerCleared = false; + +static void TestWeakGlobalHandleCallback(v8::Persistent<v8::Value> handle, + void* id) { + USE(handle); + if (1234 == reinterpret_cast<int>(id)) WeakPointerCleared = true; +} + + +TEST(WeakGlobalHandlesScavenge) { + InitializeVM(); + + WeakPointerCleared = false; + + Object* i = Heap::AllocateStringFromAscii(CStrVector("fisk")); + Object* u = Heap::AllocateHeapNumber(1.12344); + + Handle<Object> h1 = GlobalHandles::Create(i); + Handle<Object> h2 = GlobalHandles::Create(u); + + GlobalHandles::MakeWeak(h2.location(), + reinterpret_cast<void*>(1234), + &TestWeakGlobalHandleCallback); + + // Scavenge treats weak pointers as normal roots. + Heap::PerformScavenge(); + + CHECK((*h1)->IsString()); + CHECK((*h2)->IsHeapNumber()); + + CHECK(!WeakPointerCleared); + CHECK(!GlobalHandles::IsNearDeath(h2.location())); + CHECK(!GlobalHandles::IsNearDeath(h1.location())); + + GlobalHandles::Destroy(h1.location()); + GlobalHandles::Destroy(h2.location()); +} + + +TEST(WeakGlobalHandlesMark) { + InitializeVM(); + + WeakPointerCleared = false; + + Object* i = Heap::AllocateStringFromAscii(CStrVector("fisk")); + Object* u = Heap::AllocateHeapNumber(1.12344); + + Handle<Object> h1 = GlobalHandles::Create(i); + Handle<Object> h2 = GlobalHandles::Create(u); + + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + CHECK(Heap::CollectGarbage(0, NEW_SPACE)); + // Make sure the object is promoted. + + GlobalHandles::MakeWeak(h2.location(), + reinterpret_cast<void*>(1234), + &TestWeakGlobalHandleCallback); + CHECK(!GlobalHandles::IsNearDeath(h1.location())); + CHECK(!GlobalHandles::IsNearDeath(h2.location())); + + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + + CHECK((*h1)->IsString()); + + CHECK(WeakPointerCleared); + CHECK(!GlobalHandles::IsNearDeath(h1.location())); + CHECK(GlobalHandles::IsNearDeath(h2.location())); + + GlobalHandles::Destroy(h1.location()); + GlobalHandles::Destroy(h2.location()); +} + +static void TestDeleteWeakGlobalHandleCallback( + v8::Persistent<v8::Value> handle, + void* id) { + if (1234 == reinterpret_cast<int>(id)) WeakPointerCleared = true; + handle.Dispose(); +} + +TEST(DeleteWeakGlobalHandle) { + InitializeVM(); + + WeakPointerCleared = false; + + Object* i = Heap::AllocateStringFromAscii(CStrVector("fisk")); + Handle<Object> h = GlobalHandles::Create(i); + + GlobalHandles::MakeWeak(h.location(), + reinterpret_cast<void*>(1234), + &TestDeleteWeakGlobalHandleCallback); + + // Scanvenge does not recognize weak reference. + Heap::PerformScavenge(); + + CHECK(!WeakPointerCleared); + + // Mark-compact treats weak reference properly. + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + + CHECK(WeakPointerCleared); +} + +static const char* not_so_random_string_table[] = { + "abstract", + "boolean", + "break", + "byte", + "case", + "catch", + "char", + "class", + "const", + "continue", + "debugger", + "default", + "delete", + "do", + "double", + "else", + "enum", + "export", + "extends", + "false", + "final", + "finally", + "float", + "for", + "function", + "goto", + "if", + "implements", + "import", + "in", + "instanceof", + "int", + "interface", + "long", + "native", + "new", + "null", + "package", + "private", + "protected", + "public", + "return", + "short", + "static", + "super", + "switch", + "synchronized", + "this", + "throw", + "throws", + "transient", + "true", + "try", + "typeof", + "var", + "void", + "volatile", + "while", + "with", + 0 +}; + + +static void CheckSymbols(const char** strings) { + for (const char* string = *strings; *strings != 0; string = *strings++) { + Object* a = Heap::LookupAsciiSymbol(string); + CHECK(a->IsSymbol()); + Object* b = Heap::LookupAsciiSymbol(string); + CHECK_EQ(b, a); + CHECK(String::cast(b)->IsEqualTo(CStrVector(string))); + } +} + + +TEST(SymbolTable) { + InitializeVM(); + + CheckSymbols(not_so_random_string_table); + CheckSymbols(not_so_random_string_table); +} + + +TEST(FunctionAllocation) { + InitializeVM(); + + v8::HandleScope sc; + String* name = String::cast(Heap::LookupAsciiSymbol("theFunction")); + SharedFunctionInfo* function_share = + SharedFunctionInfo::cast(Heap::AllocateSharedFunctionInfo(name)); + JSFunction* function = + JSFunction::cast(Heap::AllocateFunction(*Top::function_map(), + function_share, + Heap::undefined_value())); + Map* initial_map = + Map::cast(Heap::AllocateMap(JS_OBJECT_TYPE, JSObject::kHeaderSize)); + function->set_initial_map(initial_map); + + String* prop_name = String::cast(Heap::LookupAsciiSymbol("theSlot")); + JSObject* obj = JSObject::cast(Heap::AllocateJSObject(function)); + obj->SetProperty(prop_name, Smi::FromInt(23), NONE); + CHECK_EQ(Smi::FromInt(23), obj->GetProperty(prop_name)); + // Check that we can add properties to function objects. + function->SetProperty(prop_name, Smi::FromInt(24), NONE); + CHECK_EQ(Smi::FromInt(24), function->GetProperty(prop_name)); +} + + +TEST(ObjectProperties) { + InitializeVM(); + + v8::HandleScope sc; + JSFunction* constructor = + JSFunction::cast( + Top::context()->global()->GetProperty(String::cast( + Heap::Object_symbol()))); + JSObject* obj = JSObject::cast(Heap::AllocateJSObject(constructor)); + String* first = String::cast(Heap::LookupAsciiSymbol("first")); + String* second = String::cast(Heap::LookupAsciiSymbol("second")); + + // check for empty + CHECK(!obj->HasLocalProperty(first)); + + // add first + obj->SetProperty(first, Smi::FromInt(1), NONE); + CHECK(obj->HasLocalProperty(first)); + + // delete first + CHECK(obj->DeleteProperty(first)); + CHECK(!obj->HasLocalProperty(first)); + + // add first and then second + obj->SetProperty(first, Smi::FromInt(1), NONE); + obj->SetProperty(second, Smi::FromInt(2), NONE); + CHECK(obj->HasLocalProperty(first)); + CHECK(obj->HasLocalProperty(second)); + + // delete first and then second + CHECK(obj->DeleteProperty(first)); + CHECK(obj->HasLocalProperty(second)); + CHECK(obj->DeleteProperty(second)); + CHECK(!obj->HasLocalProperty(first)); + CHECK(!obj->HasLocalProperty(second)); + + // add first and then second + obj->SetProperty(first, Smi::FromInt(1), NONE); + obj->SetProperty(second, Smi::FromInt(2), NONE); + CHECK(obj->HasLocalProperty(first)); + CHECK(obj->HasLocalProperty(second)); + + // delete second and then first + CHECK(obj->DeleteProperty(second)); + CHECK(obj->HasLocalProperty(first)); + CHECK(obj->DeleteProperty(first)); + CHECK(!obj->HasLocalProperty(first)); + CHECK(!obj->HasLocalProperty(second)); + + // check string and symbol match + static const char* string1 = "fisk"; + String* s1 = + String::cast(Heap::AllocateStringFromAscii(CStrVector(string1))); + obj->SetProperty(s1, Smi::FromInt(1), NONE); + CHECK(obj->HasLocalProperty(String::cast(Heap::LookupAsciiSymbol(string1)))); + + // check symbol and string match + static const char* string2 = "fugl"; + String* s2 = String::cast(Heap::LookupAsciiSymbol(string2)); + obj->SetProperty(s2, Smi::FromInt(1), NONE); + CHECK(obj->HasLocalProperty( + String::cast(Heap::AllocateStringFromAscii(CStrVector(string2))))); +} + + +TEST(JSObjectMaps) { + InitializeVM(); + + v8::HandleScope sc; + String* name = String::cast(Heap::LookupAsciiSymbol("theFunction")); + SharedFunctionInfo* function_share = + SharedFunctionInfo::cast(Heap::AllocateSharedFunctionInfo(name)); + JSFunction* function = + JSFunction::cast(Heap::AllocateFunction(*Top::function_map(), + function_share, + Heap::undefined_value())); + Map* initial_map = + Map::cast(Heap::AllocateMap(JS_OBJECT_TYPE, JSObject::kHeaderSize)); + function->set_initial_map(initial_map); + String* prop_name = String::cast(Heap::LookupAsciiSymbol("theSlot")); + JSObject* obj = JSObject::cast(Heap::AllocateJSObject(function)); + + // Set a propery + obj->SetProperty(prop_name, Smi::FromInt(23), NONE); + CHECK_EQ(Smi::FromInt(23), obj->GetProperty(prop_name)); + + // Check the map has changed + CHECK(initial_map != obj->map()); +} + + +TEST(JSArray) { + InitializeVM(); + + v8::HandleScope sc; + String* name = String::cast(Heap::LookupAsciiSymbol("Array")); + JSFunction* function = + JSFunction::cast(Top::context()->global()->GetProperty(name)); + + // Allocate the object. + JSArray* array = JSArray::cast(Heap::AllocateJSObject(function)); + array->Initialize(0); + + // Set array length to 0. + array->SetElementsLength(Smi::FromInt(0)); + CHECK_EQ(Smi::FromInt(0), array->length()); + CHECK(array->HasFastElements()); // Must be in fast mode. + + // array[length] = name. + array->SetElement(0, name); + CHECK_EQ(Smi::FromInt(1), array->length()); + CHECK_EQ(array->GetElement(0), name); + + // Set array length with larger than smi value. + Object* length = Heap::NumberFromInt32(Smi::kMaxValue + 1); + array->SetElementsLength(length); + + uint32_t int_length = 0; + CHECK(Array::IndexFromObject(length, &int_length)); + CHECK_EQ(length, array->length()); + CHECK(!array->HasFastElements()); // Must be in slow mode. + + // array[length] = name. + array->SetElement(int_length, name); + uint32_t new_int_length = 0; + CHECK(Array::IndexFromObject(array->length(), &new_int_length)); + CHECK_EQ(static_cast<double>(int_length), new_int_length - 1); + CHECK_EQ(array->GetElement(int_length), name); + CHECK_EQ(array->GetElement(0), name); +} + + +TEST(JSObjectCopy) { + InitializeVM(); + + v8::HandleScope sc; + String* name = String::cast(Heap::Object_symbol()); + JSFunction* constructor = + JSFunction::cast(Top::context()->global()->GetProperty(name)); + JSObject* obj = JSObject::cast(Heap::AllocateJSObject(constructor)); + String* first = String::cast(Heap::LookupAsciiSymbol("first")); + String* second = String::cast(Heap::LookupAsciiSymbol("second")); + + obj->SetProperty(first, Smi::FromInt(1), NONE); + obj->SetProperty(second, Smi::FromInt(2), NONE); + + obj->SetElement(0, first); + obj->SetElement(1, second); + + // Make the clone. + JSObject* clone = JSObject::cast(Heap::CopyJSObject(obj)); + CHECK(clone != obj); + + CHECK_EQ(obj->GetElement(0), clone->GetElement(0)); + CHECK_EQ(obj->GetElement(1), clone->GetElement(1)); + + CHECK_EQ(obj->GetProperty(first), clone->GetProperty(first)); + CHECK_EQ(obj->GetProperty(second), clone->GetProperty(second)); + + // Flip the values. + clone->SetProperty(first, Smi::FromInt(2), NONE); + clone->SetProperty(second, Smi::FromInt(1), NONE); + + clone->SetElement(0, second); + clone->SetElement(1, first); + + CHECK_EQ(obj->GetElement(1), clone->GetElement(0)); + CHECK_EQ(obj->GetElement(0), clone->GetElement(1)); + + CHECK_EQ(obj->GetProperty(second), clone->GetProperty(first)); + CHECK_EQ(obj->GetProperty(first), clone->GetProperty(second)); +} + + +TEST(StringAllocation) { + InitializeVM(); + + + const unsigned char chars[] = { 0xe5, 0xa4, 0xa7 }; + for (int length = 0; length < 100; length++) { + v8::HandleScope scope; + char* non_ascii = NewArray<char>(3 * length + 1); + char* ascii = NewArray<char>(length + 1); + non_ascii[3 * length] = 0; + ascii[length] = 0; + for (int i = 0; i < length; i++) { + ascii[i] = 'a'; + non_ascii[3 * i] = chars[0]; + non_ascii[3 * i + 1] = chars[1]; + non_ascii[3 * i + 2] = chars[2]; + } + Handle<String> non_ascii_sym = + Factory::LookupSymbol(Vector<const char>(non_ascii, 3 * length)); + CHECK_EQ(length, non_ascii_sym->length()); + Handle<String> ascii_sym = + Factory::LookupSymbol(Vector<const char>(ascii, length)); + CHECK_EQ(length, ascii_sym->length()); + Handle<String> non_ascii_str = + Factory::NewStringFromUtf8(Vector<const char>(non_ascii, 3 * length)); + non_ascii_str->Hash(); + CHECK_EQ(length, non_ascii_str->length()); + Handle<String> ascii_str = + Factory::NewStringFromUtf8(Vector<const char>(ascii, length)); + ascii_str->Hash(); + CHECK_EQ(length, ascii_str->length()); + DeleteArray(non_ascii); + DeleteArray(ascii); + } +} + + +static int ObjectsFoundInHeap(Handle<Object> objs[], int size) { + // Count the number of objects found in the heap. + int found_count = 0; + HeapIterator iterator; + while (iterator.has_next()) { + HeapObject* obj = iterator.next(); + CHECK(obj != NULL); + for (int i = 0; i < size; i++) { + if (*objs[i] == obj) { + found_count++; + } + } + } + CHECK(!iterator.has_next()); + return found_count; +} + + +TEST(Iteration) { + InitializeVM(); + v8::HandleScope scope; + + // Array of objects to scan haep for. + const int objs_count = 6; + Handle<Object> objs[objs_count]; + int next_objs_index = 0; + + // Allocate a JS array to OLD_POINTER_SPACE and NEW_SPACE + objs[next_objs_index++] = Factory::NewJSArray(10); + objs[next_objs_index++] = Factory::NewJSArray(10, TENURED); + + // Allocate a small string to OLD_DATA_SPACE and NEW_SPACE + objs[next_objs_index++] = + Factory::NewStringFromAscii(CStrVector("abcdefghij")); + objs[next_objs_index++] = + Factory::NewStringFromAscii(CStrVector("abcdefghij"), TENURED); + + // Allocate a large string (for large object space). + int large_size = Heap::MaxHeapObjectSize() + 1; + char* str = new char[large_size]; + for (int i = 0; i < large_size - 1; ++i) str[i] = 'a'; + str[large_size - 1] = '\0'; + objs[next_objs_index++] = + Factory::NewStringFromAscii(CStrVector(str), TENURED); + delete[] str; + + // Add a Map object to look for. + objs[next_objs_index++] = Handle<Map>(HeapObject::cast(*objs[0])->map()); + + CHECK_EQ(objs_count, next_objs_index); + CHECK_EQ(objs_count, ObjectsFoundInHeap(objs, objs_count)); +} diff --git a/v8/test/cctest/test-list.cc b/v8/test/cctest/test-list.cc new file mode 100644 index 0000000..d10cdd7 --- /dev/null +++ b/v8/test/cctest/test-list.cc @@ -0,0 +1,67 @@ +// Copyright 2009 the V8 project authors. 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. + +#include <stdlib.h> +#include <string.h> +#include "v8.h" +#include "cctest.h" + +using namespace v8::internal; + +// Use a testing allocator that clears memory before deletion. +class ZeroingAllocationPolicy { + public: + static void* New(size_t size) { + // Stash the size in the first word to use for Delete. + size_t true_size = size + sizeof(size_t); + size_t* result = reinterpret_cast<size_t*>(malloc(true_size)); + if (result == NULL) return result; + *result = true_size; + return result + 1; + } + + static void Delete(void* ptr) { + size_t* true_ptr = reinterpret_cast<size_t*>(ptr) - 1; + memset(true_ptr, 0, *true_ptr); + free(true_ptr); + } +}; + +// Check that we can add (a reference to) an element of the list +// itself. +TEST(ListAdd) { + // Add elements to the list to grow it to its capacity. + List<int, ZeroingAllocationPolicy> list(4); + list.Add(1); + list.Add(2); + list.Add(3); + list.Add(4); + + // Add an existing element, the backing store should have to grow. + list.Add(list[0]); + ASSERT(list[4] == 1); +} diff --git a/v8/test/cctest/test-lock.cc b/v8/test/cctest/test-lock.cc new file mode 100644 index 0000000..5eecfce --- /dev/null +++ b/v8/test/cctest/test-lock.cc @@ -0,0 +1,63 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. +// +// Tests of the TokenLock class from lock.h + +#include <stdlib.h> + +#include "v8.h" + +#include "platform.h" +#include "cctest.h" + + +using namespace ::v8::internal; + + +// Simple test of locking logic +TEST(Simple) { + Mutex* mutex = OS::CreateMutex(); + CHECK_EQ(0, mutex->Lock()); // acquire the lock with the right token + CHECK_EQ(0, mutex->Unlock()); // can unlock with the right token + delete mutex; +} + + +TEST(MultiLock) { + Mutex* mutex = OS::CreateMutex(); + CHECK_EQ(0, mutex->Lock()); + CHECK_EQ(0, mutex->Unlock()); + delete mutex; +} + + +TEST(ShallowLock) { + Mutex* mutex = OS::CreateMutex(); + CHECK_EQ(0, mutex->Lock()); + CHECK_EQ(0, mutex->Unlock()); + CHECK_EQ(0, mutex->Lock()); + CHECK_EQ(0, mutex->Unlock()); + delete mutex; +} + + +TEST(SemaphoreTimeout) { + bool ok; + Semaphore* sem = OS::CreateSemaphore(0); + + // Semaphore not signalled - timeout. + ok = sem->Wait(0); + CHECK(!ok); + ok = sem->Wait(100); + CHECK(!ok); + ok = sem->Wait(1000); + CHECK(!ok); + + // Semaphore signalled - no timeout. + sem->Signal(); + ok = sem->Wait(0); + sem->Signal(); + ok = sem->Wait(100); + sem->Signal(); + ok = sem->Wait(1000); + CHECK(ok); +} diff --git a/v8/test/cctest/test-log-ia32.cc b/v8/test/cctest/test-log-ia32.cc new file mode 100644 index 0000000..b171339 --- /dev/null +++ b/v8/test/cctest/test-log-ia32.cc @@ -0,0 +1,314 @@ +// Copyright 2006-2009 the V8 project authors. All rights reserved. +// +// Tests of profiler-related functions from log.h + +#ifdef ENABLE_LOGGING_AND_PROFILING + +#include <stdlib.h> + +#include "v8.h" + +#include "log.h" +#include "top.h" +#include "cctest.h" + +using v8::Function; +using v8::Local; +using v8::Object; +using v8::Script; +using v8::String; +using v8::Value; + +using v8::internal::byte; +using v8::internal::Handle; +using v8::internal::JSFunction; +using v8::internal::StackTracer; +using v8::internal::TickSample; +using v8::internal::Top; + + +static v8::Persistent<v8::Context> env; + + +static struct { + StackTracer* tracer; + TickSample* sample; +} trace_env = { NULL, NULL }; + + +static void InitTraceEnv(StackTracer* tracer, TickSample* sample) { + trace_env.tracer = tracer; + trace_env.sample = sample; +} + + +static void DoTrace(unsigned int fp) { + trace_env.sample->fp = fp; + // sp is only used to define stack high bound + trace_env.sample->sp = + reinterpret_cast<unsigned int>(trace_env.sample) - 10240; + trace_env.tracer->Trace(trace_env.sample); +} + + +// Hide c_entry_fp to emulate situation when sampling is done while +// pure JS code is being executed +static void DoTraceHideCEntryFPAddress(unsigned int fp) { + v8::internal::Address saved_c_frame_fp = *(Top::c_entry_fp_address()); + CHECK(saved_c_frame_fp); + *(Top::c_entry_fp_address()) = 0; + DoTrace(fp); + *(Top::c_entry_fp_address()) = saved_c_frame_fp; +} + + +static void CheckRetAddrIsInFunction(const char* func_name, + unsigned int ret_addr, + unsigned int func_start_addr, + unsigned int func_len) { + printf("CheckRetAddrIsInFunction \"%s\": %08x %08x %08x\n", + func_name, func_start_addr, ret_addr, func_start_addr + func_len); + CHECK_GE(ret_addr, func_start_addr); + CHECK_GE(func_start_addr + func_len, ret_addr); +} + + +static void CheckRetAddrIsInJSFunction(const char* func_name, + unsigned int ret_addr, + Handle<JSFunction> func) { + v8::internal::Code* func_code = func->code(); + CheckRetAddrIsInFunction( + func_name, ret_addr, + reinterpret_cast<unsigned int>(func_code->instruction_start()), + func_code->ExecutableSize()); +} + + +// --- T r a c e E x t e n s i o n --- + +class TraceExtension : public v8::Extension { + public: + TraceExtension() : v8::Extension("v8/trace", kSource) { } + virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( + v8::Handle<String> name); + static v8::Handle<v8::Value> Trace(const v8::Arguments& args); + static v8::Handle<v8::Value> JSTrace(const v8::Arguments& args); + private: + static unsigned int GetFP(const v8::Arguments& args); + static const char* kSource; +}; + + +const char* TraceExtension::kSource = + "native function trace();" + "native function js_trace();"; + + +v8::Handle<v8::FunctionTemplate> TraceExtension::GetNativeFunction( + v8::Handle<String> name) { + if (name->Equals(String::New("trace"))) { + return v8::FunctionTemplate::New(TraceExtension::Trace); + } else if (name->Equals(String::New("js_trace"))) { + return v8::FunctionTemplate::New(TraceExtension::JSTrace); + } else { + CHECK(false); + return v8::Handle<v8::FunctionTemplate>(); + } +} + + +unsigned int TraceExtension::GetFP(const v8::Arguments& args) { + CHECK_EQ(1, args.Length()); + unsigned int fp = args[0]->Int32Value() << 2; + printf("Trace: %08x\n", fp); + return fp; +} + + +v8::Handle<v8::Value> TraceExtension::Trace(const v8::Arguments& args) { + DoTrace(GetFP(args)); + return v8::Undefined(); +} + + +v8::Handle<v8::Value> TraceExtension::JSTrace(const v8::Arguments& args) { + DoTraceHideCEntryFPAddress(GetFP(args)); + return v8::Undefined(); +} + + +static TraceExtension kTraceExtension; +v8::DeclareExtension kTraceExtensionDeclaration(&kTraceExtension); + + +static void InitializeVM() { + if (env.IsEmpty()) { + v8::HandleScope scope; + const char* extensions[] = { "v8/trace" }; + v8::ExtensionConfiguration config(1, extensions); + env = v8::Context::New(&config); + } + v8::HandleScope scope; + env->Enter(); +} + + +static Handle<JSFunction> CompileFunction(const char* source) { + return v8::Utils::OpenHandle(*Script::Compile(String::New(source))); +} + + +static void CompileRun(const char* source) { + Script::Compile(String::New(source))->Run(); +} + + +static Local<Value> GetGlobalProperty(const char* name) { + return env->Global()->Get(String::New(name)); +} + + +static Handle<JSFunction> GetGlobalJSFunction(const char* name) { + Handle<JSFunction> js_func(JSFunction::cast( + *(v8::Utils::OpenHandle( + *GetGlobalProperty(name))))); + return js_func; +} + + +static void CheckRetAddrIsInJSFunction(const char* func_name, + unsigned int ret_addr) { + CheckRetAddrIsInJSFunction(func_name, ret_addr, + GetGlobalJSFunction(func_name)); +} + + +static void SetGlobalProperty(const char* name, Local<Value> value) { + env->Global()->Set(String::New(name), value); +} + + +static bool Patch(byte* from, + size_t num, + byte* original, + byte* patch, + size_t patch_len) { + byte* to = from + num; + do { + from = static_cast<byte*>(memchr(from, *original, to - from)); + CHECK(from != NULL); + if (memcmp(original, from, patch_len) == 0) { + memcpy(from, patch, patch_len); + return true; + } else { + from++; + } + } while (to - from > 0); + return false; +} + + +// Creates a global function named 'func_name' that calls the tracing +// function 'trace_func_name' with an actual EBP register value, +// shifted right to be presented as Smi. +static void CreateTraceCallerFunction(const char* func_name, + const char* trace_func_name) { + ::v8::internal::EmbeddedVector<char, 256> trace_call_buf; + ::v8::internal::OS::SNPrintF(trace_call_buf, "%s(0x6666);", trace_func_name); + Handle<JSFunction> func = CompileFunction(trace_call_buf.start()); + CHECK(!func.is_null()); + v8::internal::Code* func_code = func->code(); + CHECK(func_code->IsCode()); + + // push 0xcccc (= 0x6666 << 1) + byte original[] = { 0x68, 0xcc, 0xcc, 0x00, 0x00 }; + // mov eax,ebp; shr eax; push eax; + byte patch[] = { 0x89, 0xe8, 0xd1, 0xe8, 0x50 }; + // Patch generated code to replace pushing of a constant with + // pushing of ebp contents in a Smi + CHECK(Patch(func_code->instruction_start(), + func_code->instruction_size(), + original, patch, sizeof(patch))); + + SetGlobalProperty(func_name, v8::ToApi<Value>(func)); +} + + +TEST(CFromJSStackTrace) { + TickSample sample; + StackTracer tracer(reinterpret_cast<unsigned int>(&sample)); + InitTraceEnv(&tracer, &sample); + + InitializeVM(); + v8::HandleScope scope; + CreateTraceCallerFunction("JSFuncDoTrace", "trace"); + CompileRun( + "function JSTrace() {" + " JSFuncDoTrace();" + "};\n" + "JSTrace();"); + CHECK_GT(sample.frames_count, 1); + // Stack sampling will start from the first JS function, i.e. "JSFuncDoTrace" + CheckRetAddrIsInJSFunction("JSFuncDoTrace", + reinterpret_cast<unsigned int>(sample.stack[0])); + CheckRetAddrIsInJSFunction("JSTrace", + reinterpret_cast<unsigned int>(sample.stack[1])); +} + + +TEST(PureJSStackTrace) { + TickSample sample; + StackTracer tracer(reinterpret_cast<unsigned int>(&sample)); + InitTraceEnv(&tracer, &sample); + + InitializeVM(); + v8::HandleScope scope; + CreateTraceCallerFunction("JSFuncDoTrace", "js_trace"); + CompileRun( + "function JSTrace() {" + " JSFuncDoTrace();" + "};\n" + "function OuterJSTrace() {" + " JSTrace();" + "};\n" + "OuterJSTrace();"); + CHECK_GT(sample.frames_count, 1); + // Stack sampling will start from the caller of JSFuncDoTrace, i.e. "JSTrace" + CheckRetAddrIsInJSFunction("JSTrace", + reinterpret_cast<unsigned int>(sample.stack[0])); + CheckRetAddrIsInJSFunction("OuterJSTrace", + reinterpret_cast<unsigned int>(sample.stack[1])); +} + + +static void CFuncDoTrace() { + unsigned int fp; +#ifdef __GNUC__ + fp = reinterpret_cast<unsigned int>(__builtin_frame_address(0)); +#elif defined _MSC_VER + __asm mov [fp], ebp // NOLINT +#endif + DoTrace(fp); +} + + +static int CFunc(int depth) { + if (depth <= 0) { + CFuncDoTrace(); + return 0; + } else { + return CFunc(depth - 1) + 1; + } +} + + +TEST(PureCStackTrace) { + TickSample sample; + StackTracer tracer(reinterpret_cast<unsigned int>(&sample)); + InitTraceEnv(&tracer, &sample); + // Check that sampler doesn't crash + CHECK_EQ(10, CFunc(10)); +} + + +#endif // ENABLE_LOGGING_AND_PROFILING diff --git a/v8/test/cctest/test-mark-compact.cc b/v8/test/cctest/test-mark-compact.cc new file mode 100644 index 0000000..53cff68 --- /dev/null +++ b/v8/test/cctest/test-mark-compact.cc @@ -0,0 +1,314 @@ +// Copyright 2006-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" + +#include "global-handles.h" +#include "snapshot.h" +#include "top.h" +#include "cctest.h" + +using namespace v8::internal; + +static v8::Persistent<v8::Context> env; + +static void InitializeVM() { + if (env.IsEmpty()) env = v8::Context::New(); + v8::HandleScope scope; + env->Enter(); +} + + +TEST(MarkingStack) { + int mem_size = 20 * kPointerSize; + byte* mem = NewArray<byte>(20*kPointerSize); + Address low = reinterpret_cast<Address>(mem); + Address high = low + mem_size; + MarkingStack s; + s.Initialize(low, high); + + Address address = NULL; + while (!s.is_full()) { + s.Push(HeapObject::FromAddress(address)); + address += kPointerSize; + } + + while (!s.is_empty()) { + Address value = s.Pop()->address(); + address -= kPointerSize; + CHECK_EQ(address, value); + } + + CHECK_EQ(NULL, address); + DeleteArray(mem); +} + + +TEST(Promotion) { + // Test the situation that some objects in new space are promoted to the + // old space + if (Snapshot::IsEnabled()) return; + + // Ensure that we get a compacting collection so that objects are promoted + // from new space. + FLAG_gc_global = true; + FLAG_always_compact = true; + Heap::ConfigureHeap(2*256*KB, 4*MB); + + InitializeVM(); + + v8::HandleScope sc; + + // Allocate a fixed array in the new space. + int array_size = + (Heap::MaxHeapObjectSize() - Array::kHeaderSize) / (kPointerSize * 4); + Object* obj = Heap::AllocateFixedArray(array_size); + CHECK(!obj->IsFailure()); + + Handle<FixedArray> array(FixedArray::cast(obj)); + + // Array should be in the new space. + CHECK(Heap::InSpace(*array, NEW_SPACE)); + + // Call the m-c collector, so array becomes an old object. + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + + // Array now sits in the old space + CHECK(Heap::InSpace(*array, OLD_POINTER_SPACE)); +} + + +TEST(NoPromotion) { + if (Snapshot::IsEnabled()) return; + Heap::ConfigureHeap(2*256*KB, 4*MB); + + // Test the situation that some objects in new space are promoted to + // the old space + InitializeVM(); + + v8::HandleScope sc; + + // Do a mark compact GC to shrink the heap. + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + + // Allocate a big Fixed array in the new space. + int size = (Heap::MaxHeapObjectSize() - Array::kHeaderSize) / kPointerSize; + Object* obj = Heap::AllocateFixedArray(size); + + Handle<FixedArray> array(FixedArray::cast(obj)); + + // Array still stays in the new space. + CHECK(Heap::InSpace(*array, NEW_SPACE)); + + // Allocate objects in the old space until out of memory. + FixedArray* host = *array; + while (true) { + Object* obj = Heap::AllocateFixedArray(100, TENURED); + if (obj->IsFailure()) break; + + host->set(0, obj); + host = FixedArray::cast(obj); + } + + // Call mark compact GC, and it should pass. + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + + // array should not be promoted because the old space is full. + CHECK(Heap::InSpace(*array, NEW_SPACE)); +} + + +TEST(MarkCompactCollector) { + InitializeVM(); + + v8::HandleScope sc; + // call mark-compact when heap is empty + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + + // keep allocating garbage in new space until it fails + const int ARRAY_SIZE = 100; + Object* array; + do { + array = Heap::AllocateFixedArray(ARRAY_SIZE); + } while (!array->IsFailure()); + CHECK(Heap::CollectGarbage(0, NEW_SPACE)); + + array = Heap::AllocateFixedArray(ARRAY_SIZE); + CHECK(!array->IsFailure()); + + // keep allocating maps until it fails + Object* mapp; + do { + mapp = Heap::AllocateMap(JS_OBJECT_TYPE, JSObject::kHeaderSize); + } while (!mapp->IsFailure()); + CHECK(Heap::CollectGarbage(0, MAP_SPACE)); + mapp = Heap::AllocateMap(JS_OBJECT_TYPE, JSObject::kHeaderSize); + CHECK(!mapp->IsFailure()); + + // allocate a garbage + String* func_name = String::cast(Heap::LookupAsciiSymbol("theFunction")); + SharedFunctionInfo* function_share = + SharedFunctionInfo::cast(Heap::AllocateSharedFunctionInfo(func_name)); + JSFunction* function = + JSFunction::cast(Heap::AllocateFunction(*Top::function_map(), + function_share, + Heap::undefined_value())); + Map* initial_map = + Map::cast(Heap::AllocateMap(JS_OBJECT_TYPE, JSObject::kHeaderSize)); + function->set_initial_map(initial_map); + Top::context()->global()->SetProperty(func_name, function, NONE); + + JSObject* obj = JSObject::cast(Heap::AllocateJSObject(function)); + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + + func_name = String::cast(Heap::LookupAsciiSymbol("theFunction")); + CHECK(Top::context()->global()->HasLocalProperty(func_name)); + Object* func_value = Top::context()->global()->GetProperty(func_name); + CHECK(func_value->IsJSFunction()); + function = JSFunction::cast(func_value); + + obj = JSObject::cast(Heap::AllocateJSObject(function)); + String* obj_name = String::cast(Heap::LookupAsciiSymbol("theObject")); + Top::context()->global()->SetProperty(obj_name, obj, NONE); + String* prop_name = String::cast(Heap::LookupAsciiSymbol("theSlot")); + obj->SetProperty(prop_name, Smi::FromInt(23), NONE); + + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + + obj_name = String::cast(Heap::LookupAsciiSymbol("theObject")); + CHECK(Top::context()->global()->HasLocalProperty(obj_name)); + CHECK(Top::context()->global()->GetProperty(obj_name)->IsJSObject()); + obj = JSObject::cast(Top::context()->global()->GetProperty(obj_name)); + prop_name = String::cast(Heap::LookupAsciiSymbol("theSlot")); + CHECK(obj->GetProperty(prop_name) == Smi::FromInt(23)); +} + + +static int gc_starts = 0; +static int gc_ends = 0; + +static void GCPrologueCallbackFunc() { + CHECK(gc_starts == gc_ends); + gc_starts++; +} + + +static void GCEpilogueCallbackFunc() { + CHECK(gc_starts == gc_ends + 1); + gc_ends++; +} + + +TEST(GCCallback) { + InitializeVM(); + + Heap::SetGlobalGCPrologueCallback(&GCPrologueCallbackFunc); + Heap::SetGlobalGCEpilogueCallback(&GCEpilogueCallbackFunc); + + // Scavenge does not call GC callback functions. + Heap::PerformScavenge(); + + CHECK_EQ(0, gc_starts); + CHECK_EQ(gc_ends, gc_starts); + + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + CHECK_EQ(1, gc_starts); + CHECK_EQ(gc_ends, gc_starts); +} + + +static int NumberOfWeakCalls = 0; +static void WeakPointerCallback(v8::Persistent<v8::Value> handle, void* id) { + NumberOfWeakCalls++; +} + +TEST(ObjectGroups) { + InitializeVM(); + + NumberOfWeakCalls = 0; + v8::HandleScope handle_scope; + + Handle<Object> g1s1 = + GlobalHandles::Create(Heap::AllocateFixedArray(1)); + Handle<Object> g1s2 = + GlobalHandles::Create(Heap::AllocateFixedArray(1)); + GlobalHandles::MakeWeak(g1s1.location(), + reinterpret_cast<void*>(1234), + &WeakPointerCallback); + GlobalHandles::MakeWeak(g1s2.location(), + reinterpret_cast<void*>(1234), + &WeakPointerCallback); + + Handle<Object> g2s1 = + GlobalHandles::Create(Heap::AllocateFixedArray(1)); + Handle<Object> g2s2 = + GlobalHandles::Create(Heap::AllocateFixedArray(1)); + GlobalHandles::MakeWeak(g2s1.location(), + reinterpret_cast<void*>(1234), + &WeakPointerCallback); + GlobalHandles::MakeWeak(g2s2.location(), + reinterpret_cast<void*>(1234), + &WeakPointerCallback); + + Handle<Object> root = GlobalHandles::Create(*g1s1); // make a root. + + // Connect group 1 and 2, make a cycle. + Handle<FixedArray>::cast(g1s2)->set(0, *g2s2); + Handle<FixedArray>::cast(g2s1)->set(0, *g1s1); + + { + Object** g1_objects[] = { g1s1.location(), g1s2.location() }; + Object** g2_objects[] = { g2s1.location(), g2s2.location() }; + GlobalHandles::AddGroup(g1_objects, 2); + GlobalHandles::AddGroup(g2_objects, 2); + } + // Do a full GC + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + + // All object should be alive. + CHECK_EQ(0, NumberOfWeakCalls); + + // Weaken the root. + GlobalHandles::MakeWeak(root.location(), + reinterpret_cast<void*>(1234), + &WeakPointerCallback); + + // Groups are deleted, rebuild groups. + { + Object** g1_objects[] = { g1s1.location(), g1s2.location() }; + Object** g2_objects[] = { g2s1.location(), g2s2.location() }; + GlobalHandles::AddGroup(g1_objects, 2); + GlobalHandles::AddGroup(g2_objects, 2); + } + + CHECK(Heap::CollectGarbage(0, OLD_POINTER_SPACE)); + + // All objects should be gone. 5 global handles in total. + CHECK_EQ(5, NumberOfWeakCalls); +} diff --git a/v8/test/cctest/test-platform-linux.cc b/v8/test/cctest/test-platform-linux.cc new file mode 100644 index 0000000..e1a00e1 --- /dev/null +++ b/v8/test/cctest/test-platform-linux.cc @@ -0,0 +1,80 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. +// +// Tests of the TokenLock class from lock.h + +#include <pthread.h> +#include <stdlib.h> +#include <unistd.h> // for usleep() + +#include "v8.h" + +#include "platform.h" +#include "cctest.h" + +using namespace ::v8::internal; + + +static void yield() { + usleep(1); +} + +static const int kLockCounterLimit = 50; +static int busy_lock_counter = 0; + + +static void LoopIncrement(Mutex* mutex, int rem) { + while (true) { + int count = 0; + int last_count = -1; + do { + CHECK_EQ(0, mutex->Lock()); + count = busy_lock_counter; + CHECK_EQ(0, mutex->Unlock()); + yield(); + } while (count % 2 == rem && count < kLockCounterLimit); + if (count >= kLockCounterLimit) break; + CHECK_EQ(0, mutex->Lock()); + CHECK_EQ(count, busy_lock_counter); + CHECK(last_count == -1 || count == last_count + 1); + busy_lock_counter++; + last_count = count; + CHECK_EQ(0, mutex->Unlock()); + yield(); + } +} + + +static void* RunTestBusyLock(void* arg) { + LoopIncrement(static_cast<Mutex*>(arg), 0); + return 0; +} + + +// Runs two threads that repeatedly acquire the lock and conditionally +// increment a variable. +TEST(BusyLock) { + pthread_t other; + Mutex* mutex = OS::CreateMutex(); + int thread_created = pthread_create(&other, + NULL, + &RunTestBusyLock, + mutex); + CHECK_EQ(0, thread_created); + LoopIncrement(mutex, 1); + pthread_join(other, NULL); + delete mutex; +} + + +TEST(VirtualMemory) { + VirtualMemory* vm = new VirtualMemory(1 * MB); + CHECK(vm->IsReserved()); + void* block_addr = vm->address(); + size_t block_size = 4 * KB; + CHECK(vm->Commit(block_addr, block_size, false)); + // Check whether we can write to memory. + int* addr = static_cast<int*>(block_addr); + addr[KB-1] = 2; + CHECK(vm->Uncommit(block_addr, block_size)); + delete vm; +} diff --git a/v8/test/cctest/test-platform-macos.cc b/v8/test/cctest/test-platform-macos.cc new file mode 100644 index 0000000..d80fa54 --- /dev/null +++ b/v8/test/cctest/test-platform-macos.cc @@ -0,0 +1,10 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. +// +// Tests of the TokenLock class from lock.h + +#include <stdlib.h> + +#include "v8.h" +#include "cctest.h" + +using namespace ::v8::internal; diff --git a/v8/test/cctest/test-platform-nullos.cc b/v8/test/cctest/test-platform-nullos.cc new file mode 100644 index 0000000..c0d6ae5 --- /dev/null +++ b/v8/test/cctest/test-platform-nullos.cc @@ -0,0 +1,80 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. +// +// Tests of the TokenLock class from lock.h + +#include <pthread.h> +#include <stdlib.h> +#include <unistd.h> // for usleep() + +#include "v8.h" + +#include "platform.h" +#include "cctest.h" + +using namespace ::v8::internal; + + +static void yield() { + UNIMPLEMENTED(); +} + +static const int kLockCounterLimit = 50; +static int busy_lock_counter = 0; + + +static void LoopIncrement(Mutex* mutex, int rem) { + while (true) { + int count = 0; + int last_count = -1; + do { + CHECK_EQ(0, mutex->Lock()); + count = busy_lock_counter; + CHECK_EQ(0, mutex->Unlock()); + yield(); + } while (count % 2 == rem && count < kLockCounterLimit); + if (count >= kLockCounterLimit) break; + CHECK_EQ(0, mutex->Lock()); + CHECK_EQ(count, busy_lock_counter); + CHECK(last_count == -1 || count == last_count + 1); + busy_lock_counter++; + last_count = count; + CHECK_EQ(0, mutex->Unlock()); + yield(); + } +} + + +static void* RunTestBusyLock(void* arg) { + LoopIncrement(static_cast<Mutex*>(arg), 0); + return 0; +} + + +// Runs two threads that repeatedly acquire the lock and conditionally +// increment a variable. +TEST(BusyLock) { + pthread_t other; + Mutex* mutex = OS::CreateMutex(); + int thread_created = pthread_create(&other, + NULL, + &RunTestBusyLock, + mutex); + CHECK_EQ(0, thread_created); + LoopIncrement(mutex, 1); + pthread_join(other, NULL); + delete mutex; +} + + +TEST(VirtualMemory) { + VirtualMemory* vm = new VirtualMemory(1 * MB); + CHECK(vm->IsReserved()); + void* block_addr = vm->address(); + size_t block_size = 4 * KB; + CHECK(vm->Commit(block_addr, block_size, false)); + // Check whether we can write to memory. + int* addr = static_cast<int*>(block_addr); + addr[KB-1] = 2; + CHECK(vm->Uncommit(block_addr, block_size)); + delete vm; +} diff --git a/v8/test/cctest/test-platform-win32.cc b/v8/test/cctest/test-platform-win32.cc new file mode 100644 index 0000000..a5a6dd5 --- /dev/null +++ b/v8/test/cctest/test-platform-win32.cc @@ -0,0 +1,26 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. +// +// Tests of the TokenLock class from lock.h + +#include <stdlib.h> + +#include "v8.h" + +#include "platform.h" +#include "cctest.h" + +using namespace ::v8::internal; + + +TEST(VirtualMemory) { + VirtualMemory* vm = new VirtualMemory(1 * MB); + CHECK(vm->IsReserved()); + void* block_addr = vm->address(); + size_t block_size = 4 * KB; + CHECK(vm->Commit(block_addr, block_size, false)); + // Check whether we can write to memory. + int* addr = static_cast<int*>(block_addr); + addr[KB-1] = 2; + CHECK(vm->Uncommit(block_addr, block_size)); + delete vm; +} diff --git a/v8/test/cctest/test-regexp.cc b/v8/test/cctest/test-regexp.cc new file mode 100644 index 0000000..afd9679 --- /dev/null +++ b/v8/test/cctest/test-regexp.cc @@ -0,0 +1,1536 @@ +// Copyright 2008 the V8 project authors. 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. + + +#include <stdlib.h> +#include <set> + +#include "v8.h" + +#include "string-stream.h" +#include "cctest.h" +#include "zone-inl.h" +#include "parser.h" +#include "ast.h" +#include "jsregexp-inl.h" +#include "regexp-macro-assembler.h" +#include "regexp-macro-assembler-irregexp.h" +#ifdef ARM +#include "regexp-macro-assembler-arm.h" +#else // IA32 +#include "macro-assembler-ia32.h" +#include "regexp-macro-assembler-ia32.h" +#endif +#include "interpreter-irregexp.h" + + +using namespace v8::internal; + + +static SmartPointer<const char> Parse(const char* input) { + V8::Initialize(NULL); + v8::HandleScope scope; + ZoneScope zone_scope(DELETE_ON_EXIT); + FlatStringReader reader(CStrVector(input)); + RegExpCompileData result; + CHECK(v8::internal::ParseRegExp(&reader, false, &result)); + CHECK(result.tree != NULL); + CHECK(result.error.is_null()); + SmartPointer<const char> output = result.tree->ToString(); + return output; +} + +static bool CheckSimple(const char* input) { + V8::Initialize(NULL); + v8::HandleScope scope; + unibrow::Utf8InputBuffer<> buffer(input, strlen(input)); + ZoneScope zone_scope(DELETE_ON_EXIT); + FlatStringReader reader(CStrVector(input)); + RegExpCompileData result; + CHECK(v8::internal::ParseRegExp(&reader, false, &result)); + CHECK(result.tree != NULL); + CHECK(result.error.is_null()); + return result.simple; +} + +struct MinMaxPair { + int min_match; + int max_match; +}; + +static MinMaxPair CheckMinMaxMatch(const char* input) { + V8::Initialize(NULL); + v8::HandleScope scope; + unibrow::Utf8InputBuffer<> buffer(input, strlen(input)); + ZoneScope zone_scope(DELETE_ON_EXIT); + FlatStringReader reader(CStrVector(input)); + RegExpCompileData result; + CHECK(v8::internal::ParseRegExp(&reader, false, &result)); + CHECK(result.tree != NULL); + CHECK(result.error.is_null()); + int min_match = result.tree->min_match(); + int max_match = result.tree->max_match(); + MinMaxPair pair = { min_match, max_match }; + return pair; +} + + + +#define CHECK_PARSE_EQ(input, expected) CHECK_EQ(expected, *Parse(input)) +#define CHECK_SIMPLE(input, simple) CHECK_EQ(simple, CheckSimple(input)); +#define CHECK_MIN_MAX(input, min, max) \ + { MinMaxPair min_max = CheckMinMaxMatch(input); \ + CHECK_EQ(min, min_max.min_match); \ + CHECK_EQ(max, min_max.max_match); \ + } + +TEST(Parser) { + V8::Initialize(NULL); + CHECK_PARSE_EQ("abc", "'abc'"); + CHECK_PARSE_EQ("", "%"); + CHECK_PARSE_EQ("abc|def", "(| 'abc' 'def')"); + CHECK_PARSE_EQ("abc|def|ghi", "(| 'abc' 'def' 'ghi')"); + CHECK_PARSE_EQ("^xxx$", "(: @^i 'xxx' @$i)"); + CHECK_PARSE_EQ("ab\\b\\d\\bcd", "(: 'ab' @b [0-9] @b 'cd')"); + CHECK_PARSE_EQ("\\w|\\d", "(| [0-9 A-Z _ a-z] [0-9])"); + CHECK_PARSE_EQ("a*", "(# 0 - g 'a')"); + CHECK_PARSE_EQ("a*?", "(# 0 - n 'a')"); + CHECK_PARSE_EQ("abc+", "(: 'ab' (# 1 - g 'c'))"); + CHECK_PARSE_EQ("abc+?", "(: 'ab' (# 1 - n 'c'))"); + CHECK_PARSE_EQ("xyz?", "(: 'xy' (# 0 1 g 'z'))"); + CHECK_PARSE_EQ("xyz??", "(: 'xy' (# 0 1 n 'z'))"); + CHECK_PARSE_EQ("xyz{0,1}", "(: 'xy' (# 0 1 g 'z'))"); + CHECK_PARSE_EQ("xyz{0,1}?", "(: 'xy' (# 0 1 n 'z'))"); + CHECK_PARSE_EQ("xyz{93}", "(: 'xy' (# 93 93 g 'z'))"); + CHECK_PARSE_EQ("xyz{93}?", "(: 'xy' (# 93 93 n 'z'))"); + CHECK_PARSE_EQ("xyz{1,32}", "(: 'xy' (# 1 32 g 'z'))"); + CHECK_PARSE_EQ("xyz{1,32}?", "(: 'xy' (# 1 32 n 'z'))"); + CHECK_PARSE_EQ("xyz{1,}", "(: 'xy' (# 1 - g 'z'))"); + CHECK_PARSE_EQ("xyz{1,}?", "(: 'xy' (# 1 - n 'z'))"); + CHECK_PARSE_EQ("a\\fb\\nc\\rd\\te\\vf", "'a\\x0cb\\x0ac\\x0dd\\x09e\\x0bf'"); + CHECK_PARSE_EQ("a\\nb\\bc", "(: 'a\\x0ab' @b 'c')"); + CHECK_PARSE_EQ("(?:foo)", "'foo'"); + CHECK_PARSE_EQ("(?: foo )", "' foo '"); + CHECK_PARSE_EQ("(foo|bar|baz)", "(^ (| 'foo' 'bar' 'baz'))"); + CHECK_PARSE_EQ("foo|(bar|baz)|quux", "(| 'foo' (^ (| 'bar' 'baz')) 'quux')"); + CHECK_PARSE_EQ("foo(?=bar)baz", "(: 'foo' (-> + 'bar') 'baz')"); + CHECK_PARSE_EQ("foo(?!bar)baz", "(: 'foo' (-> - 'bar') 'baz')"); + CHECK_PARSE_EQ("()", "(^ %)"); + CHECK_PARSE_EQ("(?=)", "(-> + %)"); + CHECK_PARSE_EQ("[]", "^[\\x00-\\uffff]"); // Doesn't compile on windows + CHECK_PARSE_EQ("[^]", "[\\x00-\\uffff]"); // \uffff isn't in codepage 1252 + CHECK_PARSE_EQ("[x]", "[x]"); + CHECK_PARSE_EQ("[xyz]", "[x y z]"); + CHECK_PARSE_EQ("[a-zA-Z0-9]", "[a-z A-Z 0-9]"); + CHECK_PARSE_EQ("[-123]", "[- 1 2 3]"); + CHECK_PARSE_EQ("[^123]", "^[1 2 3]"); + CHECK_PARSE_EQ("]", "']'"); + CHECK_PARSE_EQ("}", "'}'"); + CHECK_PARSE_EQ("[a-b-c]", "[a-b - c]"); + CHECK_PARSE_EQ("[\\d]", "[0-9]"); + CHECK_PARSE_EQ("[x\\dz]", "[x 0-9 z]"); + CHECK_PARSE_EQ("[\\d-z]", "[0-9 - z]"); + CHECK_PARSE_EQ("[\\d-\\d]", "[0-9 - 0-9]"); + CHECK_PARSE_EQ("[z-\\d]", "[z - 0-9]"); + CHECK_PARSE_EQ("\\cj\\cJ\\ci\\cI\\ck\\cK", + "'\\x0a\\x0a\\x09\\x09\\x0b\\x0b'"); + CHECK_PARSE_EQ("\\c!", "'c!'"); + CHECK_PARSE_EQ("\\c_", "'c_'"); + CHECK_PARSE_EQ("\\c~", "'c~'"); + CHECK_PARSE_EQ("[a\\]c]", "[a ] c]"); + CHECK_PARSE_EQ("\\[\\]\\{\\}\\(\\)\\%\\^\\#\\ ", "'[]{}()%^# '"); + CHECK_PARSE_EQ("[\\[\\]\\{\\}\\(\\)\\%\\^\\#\\ ]", "[[ ] { } ( ) % ^ # ]"); + CHECK_PARSE_EQ("\\0", "'\\x00'"); + CHECK_PARSE_EQ("\\8", "'8'"); + CHECK_PARSE_EQ("\\9", "'9'"); + CHECK_PARSE_EQ("\\11", "'\\x09'"); + CHECK_PARSE_EQ("\\11a", "'\\x09a'"); + CHECK_PARSE_EQ("\\011", "'\\x09'"); + CHECK_PARSE_EQ("\\00011", "'\\x0011'"); + CHECK_PARSE_EQ("\\118", "'\\x098'"); + CHECK_PARSE_EQ("\\111", "'I'"); + CHECK_PARSE_EQ("\\1111", "'I1'"); + CHECK_PARSE_EQ("(x)(x)(x)\\1", "(: (^ 'x') (^ 'x') (^ 'x') (<- 1))"); + CHECK_PARSE_EQ("(x)(x)(x)\\2", "(: (^ 'x') (^ 'x') (^ 'x') (<- 2))"); + CHECK_PARSE_EQ("(x)(x)(x)\\3", "(: (^ 'x') (^ 'x') (^ 'x') (<- 3))"); + CHECK_PARSE_EQ("(x)(x)(x)\\4", "(: (^ 'x') (^ 'x') (^ 'x') '\\x04')"); + CHECK_PARSE_EQ("(x)(x)(x)\\1*", "(: (^ 'x') (^ 'x') (^ 'x')" + " (# 0 - g (<- 1)))"); + CHECK_PARSE_EQ("(x)(x)(x)\\2*", "(: (^ 'x') (^ 'x') (^ 'x')" + " (# 0 - g (<- 2)))"); + CHECK_PARSE_EQ("(x)(x)(x)\\3*", "(: (^ 'x') (^ 'x') (^ 'x')" + " (# 0 - g (<- 3)))"); + CHECK_PARSE_EQ("(x)(x)(x)\\4*", "(: (^ 'x') (^ 'x') (^ 'x')" + " (# 0 - g '\\x04'))"); + CHECK_PARSE_EQ("(x)(x)(x)(x)(x)(x)(x)(x)(x)(x)\\10", + "(: (^ 'x') (^ 'x') (^ 'x') (^ 'x') (^ 'x') (^ 'x')" + " (^ 'x') (^ 'x') (^ 'x') (^ 'x') (<- 10))"); + CHECK_PARSE_EQ("(x)(x)(x)(x)(x)(x)(x)(x)(x)(x)\\11", + "(: (^ 'x') (^ 'x') (^ 'x') (^ 'x') (^ 'x') (^ 'x')" + " (^ 'x') (^ 'x') (^ 'x') (^ 'x') '\\x09')"); + CHECK_PARSE_EQ("(a)\\1", "(: (^ 'a') (<- 1))"); + CHECK_PARSE_EQ("(a\\1)", "(^ 'a')"); + CHECK_PARSE_EQ("(\\1a)", "(^ 'a')"); + CHECK_PARSE_EQ("(?=a)?a", "'a'"); + CHECK_PARSE_EQ("(?=a){0,10}a", "'a'"); + CHECK_PARSE_EQ("(?=a){1,10}a", "(: (-> + 'a') 'a')"); + CHECK_PARSE_EQ("(?=a){9,10}a", "(: (-> + 'a') 'a')"); + CHECK_PARSE_EQ("(?!a)?a", "'a'"); + CHECK_PARSE_EQ("\\1(a)", "(^ 'a')"); + CHECK_PARSE_EQ("(?!(a))\\1", "(-> - (^ 'a'))"); + CHECK_PARSE_EQ("(?!\\1(a\\1)\\1)\\1", "(-> - (: (^ 'a') (<- 1)))"); + CHECK_PARSE_EQ("[\\0]", "[\\x00]"); + CHECK_PARSE_EQ("[\\11]", "[\\x09]"); + CHECK_PARSE_EQ("[\\11a]", "[\\x09 a]"); + CHECK_PARSE_EQ("[\\011]", "[\\x09]"); + CHECK_PARSE_EQ("[\\00011]", "[\\x00 1 1]"); + CHECK_PARSE_EQ("[\\118]", "[\\x09 8]"); + CHECK_PARSE_EQ("[\\111]", "[I]"); + CHECK_PARSE_EQ("[\\1111]", "[I 1]"); + CHECK_PARSE_EQ("\\x34", "'\x34'"); + CHECK_PARSE_EQ("\\x60", "'\x60'"); + CHECK_PARSE_EQ("\\x3z", "'x3z'"); + CHECK_PARSE_EQ("\\c", "'c'"); + CHECK_PARSE_EQ("\\u0034", "'\x34'"); + CHECK_PARSE_EQ("\\u003z", "'u003z'"); + CHECK_PARSE_EQ("foo[z]*", "(: 'foo' (# 0 - g [z]))"); + + CHECK_SIMPLE("a", true); + CHECK_SIMPLE("a|b", false); + CHECK_SIMPLE("a\\n", false); + CHECK_SIMPLE("^a", false); + CHECK_SIMPLE("a$", false); + CHECK_SIMPLE("a\\b!", false); + CHECK_SIMPLE("a\\Bb", false); + CHECK_SIMPLE("a*", false); + CHECK_SIMPLE("a*?", false); + CHECK_SIMPLE("a?", false); + CHECK_SIMPLE("a??", false); + CHECK_SIMPLE("a{0,1}?", false); + CHECK_SIMPLE("a{1,1}?", false); + CHECK_SIMPLE("a{1,2}?", false); + CHECK_SIMPLE("a+?", false); + CHECK_SIMPLE("(a)", false); + CHECK_SIMPLE("(a)\\1", false); + CHECK_SIMPLE("(\\1a)", false); + CHECK_SIMPLE("\\1(a)", false); + CHECK_SIMPLE("a\\s", false); + CHECK_SIMPLE("a\\S", false); + CHECK_SIMPLE("a\\d", false); + CHECK_SIMPLE("a\\D", false); + CHECK_SIMPLE("a\\w", false); + CHECK_SIMPLE("a\\W", false); + CHECK_SIMPLE("a.", false); + CHECK_SIMPLE("a\\q", false); + CHECK_SIMPLE("a[a]", false); + CHECK_SIMPLE("a[^a]", false); + CHECK_SIMPLE("a[a-z]", false); + CHECK_SIMPLE("a[\\q]", false); + CHECK_SIMPLE("a(?:b)", false); + CHECK_SIMPLE("a(?=b)", false); + CHECK_SIMPLE("a(?!b)", false); + CHECK_SIMPLE("\\x60", false); + CHECK_SIMPLE("\\u0060", false); + CHECK_SIMPLE("\\cA", false); + CHECK_SIMPLE("\\q", false); + CHECK_SIMPLE("\\1112", false); + CHECK_SIMPLE("\\0", false); + CHECK_SIMPLE("(a)\\1", false); + CHECK_SIMPLE("(?=a)?a", false); + CHECK_SIMPLE("(?!a)?a\\1", false); + CHECK_SIMPLE("(?:(?=a))a\\1", false); + + CHECK_PARSE_EQ("a{}", "'a{}'"); + CHECK_PARSE_EQ("a{,}", "'a{,}'"); + CHECK_PARSE_EQ("a{", "'a{'"); + CHECK_PARSE_EQ("a{z}", "'a{z}'"); + CHECK_PARSE_EQ("a{1z}", "'a{1z}'"); + CHECK_PARSE_EQ("a{12z}", "'a{12z}'"); + CHECK_PARSE_EQ("a{12,", "'a{12,'"); + CHECK_PARSE_EQ("a{12,3b", "'a{12,3b'"); + CHECK_PARSE_EQ("{}", "'{}'"); + CHECK_PARSE_EQ("{,}", "'{,}'"); + CHECK_PARSE_EQ("{", "'{'"); + CHECK_PARSE_EQ("{z}", "'{z}'"); + CHECK_PARSE_EQ("{1z}", "'{1z}'"); + CHECK_PARSE_EQ("{12z}", "'{12z}'"); + CHECK_PARSE_EQ("{12,", "'{12,'"); + CHECK_PARSE_EQ("{12,3b", "'{12,3b'"); + + CHECK_MIN_MAX("a", 1, 1); + CHECK_MIN_MAX("abc", 3, 3); + CHECK_MIN_MAX("a[bc]d", 3, 3); + CHECK_MIN_MAX("a|bc", 1, 2); + CHECK_MIN_MAX("ab|c", 1, 2); + CHECK_MIN_MAX("a||bc", 0, 2); + CHECK_MIN_MAX("|", 0, 0); + CHECK_MIN_MAX("(?:ab)", 2, 2); + CHECK_MIN_MAX("(?:ab|cde)", 2, 3); + CHECK_MIN_MAX("(?:ab)|cde", 2, 3); + CHECK_MIN_MAX("(ab)", 2, 2); + CHECK_MIN_MAX("(ab|cde)", 2, 3); + CHECK_MIN_MAX("(ab)\\1", 2, 4); + CHECK_MIN_MAX("(ab|cde)\\1", 2, 6); + CHECK_MIN_MAX("(?:ab)?", 0, 2); + CHECK_MIN_MAX("(?:ab)*", 0, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:ab)+", 2, RegExpTree::kInfinity); + CHECK_MIN_MAX("a?", 0, 1); + CHECK_MIN_MAX("a*", 0, RegExpTree::kInfinity); + CHECK_MIN_MAX("a+", 1, RegExpTree::kInfinity); + CHECK_MIN_MAX("a??", 0, 1); + CHECK_MIN_MAX("a*?", 0, RegExpTree::kInfinity); + CHECK_MIN_MAX("a+?", 1, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:a?)?", 0, 1); + CHECK_MIN_MAX("(?:a*)?", 0, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:a+)?", 0, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:a?)+", 0, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:a*)+", 0, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:a+)+", 1, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:a?)*", 0, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:a*)*", 0, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:a+)*", 0, RegExpTree::kInfinity); + CHECK_MIN_MAX("a{0}", 0, 0); + CHECK_MIN_MAX("(?:a+){0}", 0, 0); + CHECK_MIN_MAX("(?:a+){0,0}", 0, 0); + CHECK_MIN_MAX("a*b", 1, RegExpTree::kInfinity); + CHECK_MIN_MAX("a+b", 2, RegExpTree::kInfinity); + CHECK_MIN_MAX("a*b|c", 1, RegExpTree::kInfinity); + CHECK_MIN_MAX("a+b|c", 1, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:a{5,1000000}){3,1000000}", 15, RegExpTree::kInfinity); + CHECK_MIN_MAX("(?:ab){4,7}", 8, 14); + CHECK_MIN_MAX("a\\bc", 2, 2); + CHECK_MIN_MAX("a\\Bc", 2, 2); + CHECK_MIN_MAX("a\\sc", 3, 3); + CHECK_MIN_MAX("a\\Sc", 3, 3); + CHECK_MIN_MAX("a(?=b)c", 2, 2); + CHECK_MIN_MAX("a(?=bbb|bb)c", 2, 2); + CHECK_MIN_MAX("a(?!bbb|bb)c", 2, 2); +} + +TEST(ParserRegression) { + CHECK_PARSE_EQ("[A-Z$-][x]", "(! [A-Z $ -] [x])"); + CHECK_PARSE_EQ("a{3,4*}", "(: 'a{3,' (# 0 - g '4') '}')"); + CHECK_PARSE_EQ("{", "'{'"); + CHECK_PARSE_EQ("a|", "(| 'a' %)"); +} + +static void ExpectError(const char* input, + const char* expected) { + V8::Initialize(NULL); + v8::HandleScope scope; + ZoneScope zone_scope(DELETE_ON_EXIT); + FlatStringReader reader(CStrVector(input)); + RegExpCompileData result; + CHECK_EQ(false, v8::internal::ParseRegExp(&reader, false, &result)); + CHECK(result.tree == NULL); + CHECK(!result.error.is_null()); + SmartPointer<char> str = result.error->ToCString(ALLOW_NULLS); + CHECK_EQ(expected, *str); +} + + +TEST(Errors) { + V8::Initialize(NULL); + const char* kEndBackslash = "\\ at end of pattern"; + ExpectError("\\", kEndBackslash); + const char* kUnterminatedGroup = "Unterminated group"; + ExpectError("(foo", kUnterminatedGroup); + const char* kInvalidGroup = "Invalid group"; + ExpectError("(?", kInvalidGroup); + const char* kUnterminatedCharacterClass = "Unterminated character class"; + ExpectError("[", kUnterminatedCharacterClass); + ExpectError("[a-", kUnterminatedCharacterClass); + const char* kNothingToRepeat = "Nothing to repeat"; + ExpectError("*", kNothingToRepeat); + ExpectError("?", kNothingToRepeat); + ExpectError("+", kNothingToRepeat); + ExpectError("{1}", kNothingToRepeat); + ExpectError("{1,2}", kNothingToRepeat); + ExpectError("{1,}", kNothingToRepeat); + + // Check that we don't allow more than kMaxCapture captures + const int kMaxCaptures = 1 << 16; // Must match RegExpParser::kMaxCaptures. + const char* kTooManyCaptures = "Too many captures"; + HeapStringAllocator allocator; + StringStream accumulator(&allocator); + for (int i = 0; i <= kMaxCaptures; i++) { + accumulator.Add("()"); + } + SmartPointer<const char> many_captures(accumulator.ToCString()); + ExpectError(*many_captures, kTooManyCaptures); +} + + +static bool IsDigit(uc16 c) { + return ('0' <= c && c <= '9'); +} + + +static bool NotDigit(uc16 c) { + return !IsDigit(c); +} + + +static bool IsWhiteSpace(uc16 c) { + switch (c) { + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0d: + case 0x20: + case 0xA0: + case 0x2028: + case 0x2029: + return true; + default: + return unibrow::Space::Is(c); + } +} + + +static bool NotWhiteSpace(uc16 c) { + return !IsWhiteSpace(c); +} + + +static bool NotWord(uc16 c) { + return !IsRegExpWord(c); +} + + +static void TestCharacterClassEscapes(uc16 c, bool (pred)(uc16 c)) { + ZoneScope scope(DELETE_ON_EXIT); + ZoneList<CharacterRange>* ranges = new ZoneList<CharacterRange>(2); + CharacterRange::AddClassEscape(c, ranges); + for (unsigned i = 0; i < (1 << 16); i++) { + bool in_class = false; + for (int j = 0; !in_class && j < ranges->length(); j++) { + CharacterRange& range = ranges->at(j); + in_class = (range.from() <= i && i <= range.to()); + } + CHECK_EQ(pred(i), in_class); + } +} + + +TEST(CharacterClassEscapes) { + TestCharacterClassEscapes('.', IsRegExpNewline); + TestCharacterClassEscapes('d', IsDigit); + TestCharacterClassEscapes('D', NotDigit); + TestCharacterClassEscapes('s', IsWhiteSpace); + TestCharacterClassEscapes('S', NotWhiteSpace); + TestCharacterClassEscapes('w', IsRegExpWord); + TestCharacterClassEscapes('W', NotWord); +} + + +static RegExpNode* Compile(const char* input, bool multiline, bool is_ascii) { + V8::Initialize(NULL); + FlatStringReader reader(CStrVector(input)); + RegExpCompileData compile_data; + if (!v8::internal::ParseRegExp(&reader, multiline, &compile_data)) + return NULL; + Handle<String> pattern = Factory::NewStringFromUtf8(CStrVector(input)); + RegExpEngine::Compile(&compile_data, false, multiline, pattern, is_ascii); + return compile_data.node; +} + + +static void Execute(const char* input, + bool multiline, + bool is_ascii, + bool dot_output = false) { + v8::HandleScope scope; + ZoneScope zone_scope(DELETE_ON_EXIT); + RegExpNode* node = Compile(input, multiline, is_ascii); + USE(node); +#ifdef DEBUG + if (dot_output) { + RegExpEngine::DotPrint(input, node, false); + exit(0); + } +#endif // DEBUG +} + + +class TestConfig { + public: + typedef int Key; + typedef int Value; + static const int kNoKey; + static const int kNoValue; + static inline int Compare(int a, int b) { + if (a < b) + return -1; + else if (a > b) + return 1; + else + return 0; + } +}; + + +const int TestConfig::kNoKey = 0; +const int TestConfig::kNoValue = 0; + + +static int PseudoRandom(int i, int j) { + return ~(~((i * 781) ^ (j * 329))); +} + + +TEST(SplayTreeSimple) { + static const int kLimit = 1000; + ZoneScope zone_scope(DELETE_ON_EXIT); + ZoneSplayTree<TestConfig> tree; + std::set<int> seen; +#define CHECK_MAPS_EQUAL() do { \ + for (int k = 0; k < kLimit; k++) \ + CHECK_EQ(seen.find(k) != seen.end(), tree.Find(k, &loc)); \ + } while (false) + for (int i = 0; i < 50; i++) { + for (int j = 0; j < 50; j++) { + int next = PseudoRandom(i, j) % kLimit; + if (seen.find(next) != seen.end()) { + // We've already seen this one. Check the value and remove + // it. + ZoneSplayTree<TestConfig>::Locator loc; + CHECK(tree.Find(next, &loc)); + CHECK_EQ(next, loc.key()); + CHECK_EQ(3 * next, loc.value()); + tree.Remove(next); + seen.erase(next); + CHECK_MAPS_EQUAL(); + } else { + // Check that it wasn't there already and then add it. + ZoneSplayTree<TestConfig>::Locator loc; + CHECK(!tree.Find(next, &loc)); + CHECK(tree.Insert(next, &loc)); + CHECK_EQ(next, loc.key()); + loc.set_value(3 * next); + seen.insert(next); + CHECK_MAPS_EQUAL(); + } + int val = PseudoRandom(j, i) % kLimit; + for (int k = val; k >= 0; k--) { + if (seen.find(val) != seen.end()) { + ZoneSplayTree<TestConfig>::Locator loc; + CHECK(tree.FindGreatestLessThan(val, &loc)); + CHECK_EQ(loc.key(), val); + break; + } + } + val = PseudoRandom(i + j, i - j) % kLimit; + for (int k = val; k < kLimit; k++) { + if (seen.find(val) != seen.end()) { + ZoneSplayTree<TestConfig>::Locator loc; + CHECK(tree.FindLeastGreaterThan(val, &loc)); + CHECK_EQ(loc.key(), val); + break; + } + } + } + } +} + + +TEST(DispatchTableConstruction) { + // Initialize test data. + static const int kLimit = 1000; + static const int kRangeCount = 8; + static const int kRangeSize = 16; + uc16 ranges[kRangeCount][2 * kRangeSize]; + for (int i = 0; i < kRangeCount; i++) { + Vector<uc16> range(ranges[i], 2 * kRangeSize); + for (int j = 0; j < 2 * kRangeSize; j++) { + range[j] = PseudoRandom(i + 25, j + 87) % kLimit; + } + range.Sort(); + for (int j = 1; j < 2 * kRangeSize; j++) { + CHECK(range[j-1] <= range[j]); + } + } + // Enter test data into dispatch table. + ZoneScope zone_scope(DELETE_ON_EXIT); + DispatchTable table; + for (int i = 0; i < kRangeCount; i++) { + uc16* range = ranges[i]; + for (int j = 0; j < 2 * kRangeSize; j += 2) + table.AddRange(CharacterRange(range[j], range[j + 1]), i); + } + // Check that the table looks as we would expect + for (int p = 0; p < kLimit; p++) { + OutSet* outs = table.Get(p); + for (int j = 0; j < kRangeCount; j++) { + uc16* range = ranges[j]; + bool is_on = false; + for (int k = 0; !is_on && (k < 2 * kRangeSize); k += 2) + is_on = (range[k] <= p && p <= range[k + 1]); + CHECK_EQ(is_on, outs->Get(j)); + } + } +} + + +TEST(MacroAssembler) { + V8::Initialize(NULL); + byte codes[1024]; + RegExpMacroAssemblerIrregexp m(Vector<byte>(codes, 1024)); + // ^f(o)o. + Label fail, fail2, start; + uc16 foo_chars[3]; + foo_chars[0] = 'f'; + foo_chars[1] = 'o'; + foo_chars[2] = 'o'; + Vector<const uc16> foo(foo_chars, 3); + m.SetRegister(4, 42); + m.PushRegister(4, RegExpMacroAssembler::kNoStackLimitCheck); + m.AdvanceRegister(4, 42); + m.GoTo(&start); + m.Fail(); + m.Bind(&start); + m.PushBacktrack(&fail2); + m.CheckCharacters(foo, 0, &fail, true); + m.WriteCurrentPositionToRegister(0, 0); + m.PushCurrentPosition(); + m.AdvanceCurrentPosition(3); + m.WriteCurrentPositionToRegister(1, 0); + m.PopCurrentPosition(); + m.AdvanceCurrentPosition(1); + m.WriteCurrentPositionToRegister(2, 0); + m.AdvanceCurrentPosition(1); + m.WriteCurrentPositionToRegister(3, 0); + m.Succeed(); + + m.Bind(&fail); + m.Backtrack(); + m.Succeed(); + + m.Bind(&fail2); + m.PopRegister(0); + m.Fail(); + + v8::HandleScope scope; + + Handle<String> source = Factory::NewStringFromAscii(CStrVector("^f(o)o")); + Handle<ByteArray> array = Handle<ByteArray>::cast(m.GetCode(source)); + int captures[5]; + + const uc16 str1[] = {'f', 'o', 'o', 'b', 'a', 'r'}; + Handle<String> f1_16 = + Factory::NewStringFromTwoByte(Vector<const uc16>(str1, 6)); + + CHECK(IrregexpInterpreter::Match(array, f1_16, captures, 0)); + CHECK_EQ(0, captures[0]); + CHECK_EQ(3, captures[1]); + CHECK_EQ(1, captures[2]); + CHECK_EQ(2, captures[3]); + CHECK_EQ(84, captures[4]); + + const uc16 str2[] = {'b', 'a', 'r', 'f', 'o', 'o'}; + Handle<String> f2_16 = + Factory::NewStringFromTwoByte(Vector<const uc16>(str2, 6)); + + CHECK(!IrregexpInterpreter::Match(array, f2_16, captures, 0)); + CHECK_EQ(42, captures[0]); +} + + +#ifndef ARM // IA32 only tests. + +class ContextInitializer { + public: + ContextInitializer() : env_(), scope_(), stack_guard_() { + env_ = v8::Context::New(); + env_->Enter(); + } + ~ContextInitializer() { + env_->Exit(); + env_.Dispose(); + } + private: + v8::Persistent<v8::Context> env_; + v8::HandleScope scope_; + v8::internal::StackGuard stack_guard_; +}; + + +static RegExpMacroAssemblerIA32::Result ExecuteIA32(Code* code, + String* input, + int start_offset, + const byte* input_start, + const byte* input_end, + int* captures, + bool at_start) { + return RegExpMacroAssemblerIA32::Execute( + code, + input, + start_offset, + input_start, + input_end, + captures, + at_start); +} + + +TEST(MacroAssemblerIA32Success) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::ASCII, 4); + + m.Succeed(); + + Handle<String> source = Factory::NewStringFromAscii(CStrVector("")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + int captures[4] = {42, 37, 87, 117}; + Handle<String> input = Factory::NewStringFromAscii(CStrVector("foofoo")); + Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input); + const byte* start_adr = + reinterpret_cast<const byte*>(seq_input->GetCharsAddress()); + + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + seq_input->length(), + captures, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result); + CHECK_EQ(-1, captures[0]); + CHECK_EQ(-1, captures[1]); + CHECK_EQ(-1, captures[2]); + CHECK_EQ(-1, captures[3]); +} + + +TEST(MacroAssemblerIA32Simple) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::ASCII, 4); + + uc16 foo_chars[3] = {'f', 'o', 'o'}; + Vector<const uc16> foo(foo_chars, 3); + + Label fail; + m.CheckCharacters(foo, 0, &fail, true); + m.WriteCurrentPositionToRegister(0, 0); + m.AdvanceCurrentPosition(3); + m.WriteCurrentPositionToRegister(1, 0); + m.Succeed(); + m.Bind(&fail); + m.Fail(); + + Handle<String> source = Factory::NewStringFromAscii(CStrVector("^foo")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + int captures[4] = {42, 37, 87, 117}; + Handle<String> input = Factory::NewStringFromAscii(CStrVector("foofoo")); + Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input); + Address start_adr = seq_input->GetCharsAddress(); + + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length(), + captures, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result); + CHECK_EQ(0, captures[0]); + CHECK_EQ(3, captures[1]); + CHECK_EQ(-1, captures[2]); + CHECK_EQ(-1, captures[3]); + + input = Factory::NewStringFromAscii(CStrVector("barbarbar")); + seq_input = Handle<SeqAsciiString>::cast(input); + start_adr = seq_input->GetCharsAddress(); + + result = ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length(), + captures, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::FAILURE, result); +} + + +TEST(MacroAssemblerIA32SimpleUC16) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::UC16, 4); + + uc16 foo_chars[3] = {'f', 'o', 'o'}; + Vector<const uc16> foo(foo_chars, 3); + + Label fail; + m.CheckCharacters(foo, 0, &fail, true); + m.WriteCurrentPositionToRegister(0, 0); + m.AdvanceCurrentPosition(3); + m.WriteCurrentPositionToRegister(1, 0); + m.Succeed(); + m.Bind(&fail); + m.Fail(); + + Handle<String> source = Factory::NewStringFromAscii(CStrVector("^foo")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + int captures[4] = {42, 37, 87, 117}; + const uc16 input_data[6] = {'f', 'o', 'o', 'f', 'o', '\xa0'}; + Handle<String> input = + Factory::NewStringFromTwoByte(Vector<const uc16>(input_data, 6)); + Handle<SeqTwoByteString> seq_input = Handle<SeqTwoByteString>::cast(input); + Address start_adr = seq_input->GetCharsAddress(); + + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length(), + captures, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result); + CHECK_EQ(0, captures[0]); + CHECK_EQ(3, captures[1]); + CHECK_EQ(-1, captures[2]); + CHECK_EQ(-1, captures[3]); + + const uc16 input_data2[9] = {'b', 'a', 'r', 'b', 'a', 'r', 'b', 'a', '\xa0'}; + input = Factory::NewStringFromTwoByte(Vector<const uc16>(input_data2, 9)); + seq_input = Handle<SeqTwoByteString>::cast(input); + start_adr = seq_input->GetCharsAddress(); + + result = ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length() * 2, + captures, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::FAILURE, result); +} + + +TEST(MacroAssemblerIA32Backtrack) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::ASCII, 0); + + Label fail; + Label backtrack; + m.LoadCurrentCharacter(10, &fail); + m.Succeed(); + m.Bind(&fail); + m.PushBacktrack(&backtrack); + m.LoadCurrentCharacter(10, NULL); + m.Succeed(); + m.Bind(&backtrack); + m.Fail(); + + Handle<String> source = Factory::NewStringFromAscii(CStrVector("..........")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + Handle<String> input = Factory::NewStringFromAscii(CStrVector("foofoo")); + Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input); + Address start_adr = seq_input->GetCharsAddress(); + + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length(), + NULL, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::FAILURE, result); +} + + +TEST(MacroAssemblerIA32BackReferenceASCII) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::ASCII, 3); + + m.WriteCurrentPositionToRegister(0, 0); + m.AdvanceCurrentPosition(2); + m.WriteCurrentPositionToRegister(1, 0); + Label nomatch; + m.CheckNotBackReference(0, &nomatch); + m.Fail(); + m.Bind(&nomatch); + m.AdvanceCurrentPosition(2); + Label missing_match; + m.CheckNotBackReference(0, &missing_match); + m.WriteCurrentPositionToRegister(2, 0); + m.Succeed(); + m.Bind(&missing_match); + m.Fail(); + + Handle<String> source = Factory::NewStringFromAscii(CStrVector("^(..)..\1")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + Handle<String> input = Factory::NewStringFromAscii(CStrVector("fooofo")); + Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input); + Address start_adr = seq_input->GetCharsAddress(); + + int output[3]; + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length(), + output, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result); + CHECK_EQ(0, output[0]); + CHECK_EQ(2, output[1]); + CHECK_EQ(6, output[2]); +} + + +TEST(MacroAssemblerIA32BackReferenceUC16) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::UC16, 3); + + m.WriteCurrentPositionToRegister(0, 0); + m.AdvanceCurrentPosition(2); + m.WriteCurrentPositionToRegister(1, 0); + Label nomatch; + m.CheckNotBackReference(0, &nomatch); + m.Fail(); + m.Bind(&nomatch); + m.AdvanceCurrentPosition(2); + Label missing_match; + m.CheckNotBackReference(0, &missing_match); + m.WriteCurrentPositionToRegister(2, 0); + m.Succeed(); + m.Bind(&missing_match); + m.Fail(); + + Handle<String> source = Factory::NewStringFromAscii(CStrVector("^(..)..\1")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + const uc16 input_data[6] = {'f', 0x2028, 'o', 'o', 'f', 0x2028}; + Handle<String> input = + Factory::NewStringFromTwoByte(Vector<const uc16>(input_data, 6)); + Handle<SeqTwoByteString> seq_input = Handle<SeqTwoByteString>::cast(input); + Address start_adr = seq_input->GetCharsAddress(); + + int output[3]; + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length() * 2, + output, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result); + CHECK_EQ(0, output[0]); + CHECK_EQ(2, output[1]); + CHECK_EQ(6, output[2]); +} + + + +TEST(MacroAssemblerIA32AtStart) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::ASCII, 0); + + Label not_at_start, newline, fail; + m.CheckNotAtStart(¬_at_start); + // Check that prevchar = '\n' and current = 'f'. + m.CheckCharacter('\n', &newline); + m.Bind(&fail); + m.Fail(); + m.Bind(&newline); + m.LoadCurrentCharacter(0, &fail); + m.CheckNotCharacter('f', &fail); + m.Succeed(); + + m.Bind(¬_at_start); + // Check that prevchar = 'o' and current = 'b'. + Label prevo; + m.CheckCharacter('o', &prevo); + m.Fail(); + m.Bind(&prevo); + m.LoadCurrentCharacter(0, &fail); + m.CheckNotCharacter('b', &fail); + m.Succeed(); + + Handle<String> source = Factory::NewStringFromAscii(CStrVector("(^f|ob)")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + Handle<String> input = Factory::NewStringFromAscii(CStrVector("foobar")); + Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input); + Address start_adr = seq_input->GetCharsAddress(); + + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length(), + NULL, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result); + + result = ExecuteIA32(*code, + *input, + 3, + start_adr + 3, + start_adr + input->length(), + NULL, + false); + + CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result); +} + + +TEST(MacroAssemblerIA32BackRefNoCase) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::ASCII, 4); + + Label fail, succ; + + m.WriteCurrentPositionToRegister(0, 0); + m.WriteCurrentPositionToRegister(2, 0); + m.AdvanceCurrentPosition(3); + m.WriteCurrentPositionToRegister(3, 0); + m.CheckNotBackReferenceIgnoreCase(2, &fail); // Match "AbC". + m.CheckNotBackReferenceIgnoreCase(2, &fail); // Match "ABC". + Label expected_fail; + m.CheckNotBackReferenceIgnoreCase(2, &expected_fail); + m.Bind(&fail); + m.Fail(); + + m.Bind(&expected_fail); + m.AdvanceCurrentPosition(3); // Skip "xYz" + m.CheckNotBackReferenceIgnoreCase(2, &succ); + m.Fail(); + + m.Bind(&succ); + m.WriteCurrentPositionToRegister(1, 0); + m.Succeed(); + + Handle<String> source = + Factory::NewStringFromAscii(CStrVector("^(abc)\1\1(?!\1)...(?!\1)")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + Handle<String> input = + Factory::NewStringFromAscii(CStrVector("aBcAbCABCxYzab")); + Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input); + Address start_adr = seq_input->GetCharsAddress(); + + int output[4]; + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length(), + output, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result); + CHECK_EQ(0, output[0]); + CHECK_EQ(12, output[1]); + CHECK_EQ(0, output[2]); + CHECK_EQ(3, output[3]); +} + + + +TEST(MacroAssemblerIA32Registers) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::ASCII, 5); + + uc16 foo_chars[3] = {'f', 'o', 'o'}; + Vector<const uc16> foo(foo_chars, 3); + + enum registers { out1, out2, out3, out4, out5, sp, loop_cnt }; + Label fail; + Label backtrack; + m.WriteCurrentPositionToRegister(out1, 0); // Output: [0] + m.PushRegister(out1, RegExpMacroAssembler::kNoStackLimitCheck); + m.PushBacktrack(&backtrack); + m.WriteStackPointerToRegister(sp); + // Fill stack and registers + m.AdvanceCurrentPosition(2); + m.WriteCurrentPositionToRegister(out1, 0); + m.PushRegister(out1, RegExpMacroAssembler::kNoStackLimitCheck); + m.PushBacktrack(&fail); + // Drop backtrack stack frames. + m.ReadStackPointerFromRegister(sp); + // And take the first backtrack (to &backtrack) + m.Backtrack(); + + m.PushCurrentPosition(); + m.AdvanceCurrentPosition(2); + m.PopCurrentPosition(); + + m.Bind(&backtrack); + m.PopRegister(out1); + m.ReadCurrentPositionFromRegister(out1); + m.AdvanceCurrentPosition(3); + m.WriteCurrentPositionToRegister(out2, 0); // [0,3] + + Label loop; + m.SetRegister(loop_cnt, 0); // loop counter + m.Bind(&loop); + m.AdvanceRegister(loop_cnt, 1); + m.AdvanceCurrentPosition(1); + m.IfRegisterLT(loop_cnt, 3, &loop); + m.WriteCurrentPositionToRegister(out3, 0); // [0,3,6] + + Label loop2; + m.SetRegister(loop_cnt, 2); // loop counter + m.Bind(&loop2); + m.AdvanceRegister(loop_cnt, -1); + m.AdvanceCurrentPosition(1); + m.IfRegisterGE(loop_cnt, 0, &loop2); + m.WriteCurrentPositionToRegister(out4, 0); // [0,3,6,9] + + Label loop3; + Label exit_loop3; + m.PushRegister(out4, RegExpMacroAssembler::kNoStackLimitCheck); + m.PushRegister(out4, RegExpMacroAssembler::kNoStackLimitCheck); + m.ReadCurrentPositionFromRegister(out3); + m.Bind(&loop3); + m.AdvanceCurrentPosition(1); + m.CheckGreedyLoop(&exit_loop3); + m.GoTo(&loop3); + m.Bind(&exit_loop3); + m.PopCurrentPosition(); + m.WriteCurrentPositionToRegister(out5, 0); // [0,3,6,9,9] + + m.Succeed(); + + m.Bind(&fail); + m.Fail(); + + Handle<String> source = + Factory::NewStringFromAscii(CStrVector("<loop test>")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + // String long enough for test (content doesn't matter). + Handle<String> input = + Factory::NewStringFromAscii(CStrVector("foofoofoofoofoo")); + Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input); + Address start_adr = seq_input->GetCharsAddress(); + + int output[5]; + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length(), + output, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result); + CHECK_EQ(0, output[0]); + CHECK_EQ(3, output[1]); + CHECK_EQ(6, output[2]); + CHECK_EQ(9, output[3]); + CHECK_EQ(9, output[4]); +} + + +TEST(MacroAssemblerIA32StackOverflow) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::ASCII, 0); + + Label loop; + m.Bind(&loop); + m.PushBacktrack(&loop); + m.GoTo(&loop); + + Handle<String> source = + Factory::NewStringFromAscii(CStrVector("<stack overflow test>")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + // String long enough for test (content doesn't matter). + Handle<String> input = + Factory::NewStringFromAscii(CStrVector("dummy")); + Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input); + Address start_adr = seq_input->GetCharsAddress(); + + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length(), + NULL, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::EXCEPTION, result); + CHECK(Top::has_pending_exception()); + Top::clear_pending_exception(); +} + + +TEST(MacroAssemblerIA32LotsOfRegisters) { + v8::V8::Initialize(); + ContextInitializer initializer; + + RegExpMacroAssemblerIA32 m(RegExpMacroAssemblerIA32::ASCII, 2); + + // At least 2048, to ensure the allocated space for registers + // span one full page. + const int large_number = 8000; + m.WriteCurrentPositionToRegister(large_number, 42); + m.WriteCurrentPositionToRegister(0, 0); + m.WriteCurrentPositionToRegister(1, 1); + Label done; + m.CheckNotBackReference(0, &done); // Performs a system-stack push. + m.Bind(&done); + m.PushRegister(large_number, RegExpMacroAssembler::kNoStackLimitCheck); + m.PopRegister(1); + m.Succeed(); + + Handle<String> source = + Factory::NewStringFromAscii(CStrVector("<huge register space test>")); + Handle<Object> code_object = m.GetCode(source); + Handle<Code> code = Handle<Code>::cast(code_object); + + // String long enough for test (content doesn't matter). + Handle<String> input = + Factory::NewStringFromAscii(CStrVector("sample text")); + Handle<SeqAsciiString> seq_input = Handle<SeqAsciiString>::cast(input); + Address start_adr = seq_input->GetCharsAddress(); + + int captures[2]; + RegExpMacroAssemblerIA32::Result result = + ExecuteIA32(*code, + *input, + 0, + start_adr, + start_adr + input->length(), + captures, + true); + + CHECK_EQ(RegExpMacroAssemblerIA32::SUCCESS, result); + CHECK_EQ(0, captures[0]); + CHECK_EQ(42, captures[1]); + + Top::clear_pending_exception(); +} + + + +#endif // !defined ARM + +TEST(AddInverseToTable) { + static const int kLimit = 1000; + static const int kRangeCount = 16; + for (int t = 0; t < 10; t++) { + ZoneScope zone_scope(DELETE_ON_EXIT); + ZoneList<CharacterRange>* ranges = + new ZoneList<CharacterRange>(kRangeCount); + for (int i = 0; i < kRangeCount; i++) { + int from = PseudoRandom(t + 87, i + 25) % kLimit; + int to = from + (PseudoRandom(i + 87, t + 25) % (kLimit / 20)); + if (to > kLimit) to = kLimit; + ranges->Add(CharacterRange(from, to)); + } + DispatchTable table; + DispatchTableConstructor cons(&table, false); + cons.set_choice_index(0); + cons.AddInverse(ranges); + for (int i = 0; i < kLimit; i++) { + bool is_on = false; + for (int j = 0; !is_on && j < kRangeCount; j++) + is_on = ranges->at(j).Contains(i); + OutSet* set = table.Get(i); + CHECK_EQ(is_on, set->Get(0) == false); + } + } + ZoneScope zone_scope(DELETE_ON_EXIT); + ZoneList<CharacterRange>* ranges = + new ZoneList<CharacterRange>(1); + ranges->Add(CharacterRange(0xFFF0, 0xFFFE)); + DispatchTable table; + DispatchTableConstructor cons(&table, false); + cons.set_choice_index(0); + cons.AddInverse(ranges); + CHECK(!table.Get(0xFFFE)->Get(0)); + CHECK(table.Get(0xFFFF)->Get(0)); +} + + +static uc32 canonicalize(uc32 c) { + unibrow::uchar canon[unibrow::Ecma262Canonicalize::kMaxWidth]; + int count = unibrow::Ecma262Canonicalize::Convert(c, '\0', canon, NULL); + if (count == 0) { + return c; + } else { + CHECK_EQ(1, count); + return canon[0]; + } +} + + +TEST(LatinCanonicalize) { + unibrow::Mapping<unibrow::Ecma262UnCanonicalize> un_canonicalize; + for (char lower = 'a'; lower <= 'z'; lower++) { + char upper = lower + ('A' - 'a'); + CHECK_EQ(canonicalize(lower), canonicalize(upper)); + unibrow::uchar uncanon[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + int length = un_canonicalize.get(lower, '\0', uncanon); + CHECK_EQ(2, length); + CHECK_EQ(upper, uncanon[0]); + CHECK_EQ(lower, uncanon[1]); + } + for (uc32 c = 128; c < (1 << 21); c++) + CHECK_GE(canonicalize(c), 128); + unibrow::Mapping<unibrow::ToUppercase> to_upper; + for (uc32 c = 0; c < (1 << 21); c++) { + unibrow::uchar upper[unibrow::ToUppercase::kMaxWidth]; + int length = to_upper.get(c, '\0', upper); + if (length == 0) { + length = 1; + upper[0] = c; + } + uc32 u = upper[0]; + if (length > 1 || (c >= 128 && u < 128)) + u = c; + CHECK_EQ(u, canonicalize(c)); + } +} + + +static uc32 CanonRange(uc32 c) { + unibrow::uchar canon[unibrow::CanonicalizationRange::kMaxWidth]; + int count = unibrow::CanonicalizationRange::Convert(c, '\0', canon, NULL); + if (count == 0) { + return c; + } else { + CHECK_EQ(1, count); + return canon[0]; + } +} + + +TEST(RangeCanonicalization) { + CHECK_NE(CanonRange(0) & CharacterRange::kStartMarker, 0); + // Check that we arrive at the same result when using the basic + // range canonicalization primitives as when using immediate + // canonicalization. + unibrow::Mapping<unibrow::Ecma262UnCanonicalize> un_canonicalize; + for (int i = 0; i < CharacterRange::kRangeCanonicalizeMax; i++) { + int range = CanonRange(i); + int indirect_length = 0; + unibrow::uchar indirect[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + if ((range & CharacterRange::kStartMarker) == 0) { + indirect_length = un_canonicalize.get(i - range, '\0', indirect); + for (int i = 0; i < indirect_length; i++) + indirect[i] += range; + } else { + indirect_length = un_canonicalize.get(i, '\0', indirect); + } + unibrow::uchar direct[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + int direct_length = un_canonicalize.get(i, '\0', direct); + CHECK_EQ(direct_length, indirect_length); + } + // Check that we arrive at the same results when skipping over + // canonicalization ranges. + int next_block = 0; + while (next_block < CharacterRange::kRangeCanonicalizeMax) { + uc32 start = CanonRange(next_block); + CHECK_NE((start & CharacterRange::kStartMarker), 0); + unsigned dist = start & CharacterRange::kPayloadMask; + unibrow::uchar first[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + int first_length = un_canonicalize.get(next_block, '\0', first); + for (unsigned i = 1; i < dist; i++) { + CHECK_EQ(i, CanonRange(next_block + i)); + unibrow::uchar succ[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + int succ_length = un_canonicalize.get(next_block + i, '\0', succ); + CHECK_EQ(first_length, succ_length); + for (int j = 0; j < succ_length; j++) { + int calc = first[j] + i; + int found = succ[j]; + CHECK_EQ(calc, found); + } + } + next_block = next_block + dist; + } +} + + +TEST(UncanonicalizeEquivalence) { + unibrow::Mapping<unibrow::Ecma262UnCanonicalize> un_canonicalize; + unibrow::uchar chars[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + for (int i = 0; i < (1 << 16); i++) { + int length = un_canonicalize.get(i, '\0', chars); + for (int j = 0; j < length; j++) { + unibrow::uchar chars2[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + int length2 = un_canonicalize.get(chars[j], '\0', chars2); + CHECK_EQ(length, length2); + for (int k = 0; k < length; k++) + CHECK_EQ(static_cast<int>(chars[k]), static_cast<int>(chars2[k])); + } + } +} + + +static void TestRangeCaseIndependence(CharacterRange input, + Vector<CharacterRange> expected) { + ZoneScope zone_scope(DELETE_ON_EXIT); + int count = expected.length(); + ZoneList<CharacterRange>* list = new ZoneList<CharacterRange>(count); + input.AddCaseEquivalents(list); + CHECK_EQ(count, list->length()); + for (int i = 0; i < list->length(); i++) { + CHECK_EQ(expected[i].from(), list->at(i).from()); + CHECK_EQ(expected[i].to(), list->at(i).to()); + } +} + + +static void TestSimpleRangeCaseIndependence(CharacterRange input, + CharacterRange expected) { + EmbeddedVector<CharacterRange, 1> vector; + vector[0] = expected; + TestRangeCaseIndependence(input, vector); +} + + +TEST(CharacterRangeCaseIndependence) { + TestSimpleRangeCaseIndependence(CharacterRange::Singleton('a'), + CharacterRange::Singleton('A')); + TestSimpleRangeCaseIndependence(CharacterRange::Singleton('z'), + CharacterRange::Singleton('Z')); + TestSimpleRangeCaseIndependence(CharacterRange('a', 'z'), + CharacterRange('A', 'Z')); + TestSimpleRangeCaseIndependence(CharacterRange('c', 'f'), + CharacterRange('C', 'F')); + TestSimpleRangeCaseIndependence(CharacterRange('a', 'b'), + CharacterRange('A', 'B')); + TestSimpleRangeCaseIndependence(CharacterRange('y', 'z'), + CharacterRange('Y', 'Z')); + TestSimpleRangeCaseIndependence(CharacterRange('a' - 1, 'z' + 1), + CharacterRange('A', 'Z')); + TestSimpleRangeCaseIndependence(CharacterRange('A', 'Z'), + CharacterRange('a', 'z')); + TestSimpleRangeCaseIndependence(CharacterRange('C', 'F'), + CharacterRange('c', 'f')); + TestSimpleRangeCaseIndependence(CharacterRange('A' - 1, 'Z' + 1), + CharacterRange('a', 'z')); + // Here we need to add [l-z] to complete the case independence of + // [A-Za-z] but we expect [a-z] to be added since we always add a + // whole block at a time. + TestSimpleRangeCaseIndependence(CharacterRange('A', 'k'), + CharacterRange('a', 'z')); +} + + +static bool InClass(uc16 c, ZoneList<CharacterRange>* ranges) { + if (ranges == NULL) + return false; + for (int i = 0; i < ranges->length(); i++) { + CharacterRange range = ranges->at(i); + if (range.from() <= c && c <= range.to()) + return true; + } + return false; +} + + +TEST(CharClassDifference) { + ZoneScope zone_scope(DELETE_ON_EXIT); + ZoneList<CharacterRange>* base = new ZoneList<CharacterRange>(1); + base->Add(CharacterRange::Everything()); + Vector<const uc16> overlay = CharacterRange::GetWordBounds(); + ZoneList<CharacterRange>* included = NULL; + ZoneList<CharacterRange>* excluded = NULL; + CharacterRange::Split(base, overlay, &included, &excluded); + for (int i = 0; i < (1 << 16); i++) { + bool in_base = InClass(i, base); + if (in_base) { + bool in_overlay = false; + for (int j = 0; !in_overlay && j < overlay.length(); j += 2) { + if (overlay[j] <= i && i <= overlay[j+1]) + in_overlay = true; + } + CHECK_EQ(in_overlay, InClass(i, included)); + CHECK_EQ(!in_overlay, InClass(i, excluded)); + } else { + CHECK(!InClass(i, included)); + CHECK(!InClass(i, excluded)); + } + } +} + + +TEST(Graph) { + V8::Initialize(NULL); + Execute("(?:(?:x(.))?\1)+$", false, true, true); +} diff --git a/v8/test/cctest/test-serialize.cc b/v8/test/cctest/test-serialize.cc new file mode 100644 index 0000000..56727dc --- /dev/null +++ b/v8/test/cctest/test-serialize.cc @@ -0,0 +1,268 @@ +// Copyright 2007-2008 the V8 project authors. 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. + +#include <signal.h> +#include <map> +#include <string> + +#include "sys/stat.h" +#include "v8.h" + +#include "debug.h" +#include "ic-inl.h" +#include "runtime.h" +#include "serialize.h" +#include "scopeinfo.h" +#include "snapshot.h" +#include "cctest.h" + +using namespace v8::internal; + +static int local_counters[256]; +static int counter_count = 0; +static std::map<std::string, int> counter_table; + + +// Callback receiver to track counters in test. +static int* counter_function(const char* name) { + std::string counter(name); + if (counter_table.find(counter) == counter_table.end()) { + local_counters[counter_count] = 0; + counter_table[counter] = counter_count++; + } + + return &local_counters[counter_table[counter]]; +} + + +template <class T> +static Address AddressOf(T id) { + return ExternalReference(id).address(); +} + + +template <class T> +static uint32_t Encode(const ExternalReferenceEncoder& encoder, T id) { + return encoder.Encode(AddressOf(id)); +} + + +static int make_code(TypeCode type, int id) { + return static_cast<uint32_t>(type) << kReferenceTypeShift | id; +} + + +static int register_code(int reg) { + return Debug::k_register_address << kDebugIdShift | reg; +} + + +TEST(ExternalReferenceEncoder) { + StatsTable::SetCounterFunction(counter_function); + Heap::Setup(false); + ExternalReferenceEncoder encoder; + CHECK_EQ(make_code(BUILTIN, Builtins::ArrayCode), + Encode(encoder, Builtins::ArrayCode)); + CHECK_EQ(make_code(RUNTIME_FUNCTION, Runtime::kAbort), + Encode(encoder, Runtime::kAbort)); + CHECK_EQ(make_code(IC_UTILITY, IC::kLoadCallbackProperty), + Encode(encoder, IC_Utility(IC::kLoadCallbackProperty))); + CHECK_EQ(make_code(DEBUG_ADDRESS, register_code(3)), + Encode(encoder, Debug_Address(Debug::k_register_address, 3))); + ExternalReference keyed_load_function_prototype = + ExternalReference(&Counters::keyed_load_function_prototype); + CHECK_EQ(make_code(STATS_COUNTER, Counters::k_keyed_load_function_prototype), + encoder.Encode(keyed_load_function_prototype.address())); + ExternalReference passed_function = + ExternalReference::builtin_passed_function(); + CHECK_EQ(make_code(UNCLASSIFIED, 1), + encoder.Encode(passed_function.address())); + ExternalReference the_hole_value_location = + ExternalReference::the_hole_value_location(); + CHECK_EQ(make_code(UNCLASSIFIED, 2), + encoder.Encode(the_hole_value_location.address())); + ExternalReference stack_guard_limit_address = + ExternalReference::address_of_stack_guard_limit(); + CHECK_EQ(make_code(UNCLASSIFIED, 3), + encoder.Encode(stack_guard_limit_address.address())); + CHECK_EQ(make_code(UNCLASSIFIED, 5), + encoder.Encode(ExternalReference::debug_break().address())); + CHECK_EQ(make_code(UNCLASSIFIED, 6), + encoder.Encode(ExternalReference::new_space_start().address())); +} + + +TEST(ExternalReferenceDecoder) { + StatsTable::SetCounterFunction(counter_function); + Heap::Setup(false); + ExternalReferenceDecoder decoder; + CHECK_EQ(AddressOf(Builtins::ArrayCode), + decoder.Decode(make_code(BUILTIN, Builtins::ArrayCode))); + CHECK_EQ(AddressOf(Runtime::kAbort), + decoder.Decode(make_code(RUNTIME_FUNCTION, Runtime::kAbort))); + CHECK_EQ(AddressOf(IC_Utility(IC::kLoadCallbackProperty)), + decoder.Decode(make_code(IC_UTILITY, IC::kLoadCallbackProperty))); + CHECK_EQ(AddressOf(Debug_Address(Debug::k_register_address, 3)), + decoder.Decode(make_code(DEBUG_ADDRESS, register_code(3)))); + ExternalReference keyed_load_function = + ExternalReference(&Counters::keyed_load_function_prototype); + CHECK_EQ(keyed_load_function.address(), + decoder.Decode( + make_code(STATS_COUNTER, + Counters::k_keyed_load_function_prototype))); + CHECK_EQ(ExternalReference::builtin_passed_function().address(), + decoder.Decode(make_code(UNCLASSIFIED, 1))); + CHECK_EQ(ExternalReference::the_hole_value_location().address(), + decoder.Decode(make_code(UNCLASSIFIED, 2))); + CHECK_EQ(ExternalReference::address_of_stack_guard_limit().address(), + decoder.Decode(make_code(UNCLASSIFIED, 3))); + CHECK_EQ(ExternalReference::debug_break().address(), + decoder.Decode(make_code(UNCLASSIFIED, 5))); + CHECK_EQ(ExternalReference::new_space_start().address(), + decoder.Decode(make_code(UNCLASSIFIED, 6))); +} + + +static void Serialize() { +#ifdef DEBUG + FLAG_debug_serialization = true; +#endif + StatsTable::SetCounterFunction(counter_function); + + v8::HandleScope scope; + const int kExtensionCount = 1; + const char* extension_list[kExtensionCount] = { "v8/gc" }; + v8::ExtensionConfiguration extensions(kExtensionCount, extension_list); + Serializer::Enable(); + v8::Persistent<v8::Context> env = v8::Context::New(&extensions); + env->Enter(); + + Snapshot::WriteToFile(FLAG_testing_serialization_file); +} + + +// Test that the whole heap can be serialized when running from the +// internal snapshot. +// (Smoke test.) +TEST(SerializeInternal) { + Snapshot::Initialize(NULL); + Serialize(); +} + + +// Test that the whole heap can be serialized when running from a +// bootstrapped heap. +// (Smoke test.) +TEST(Serialize) { + if (Snapshot::IsEnabled()) return; + Serialize(); +} + + +// Test that the heap isn't destroyed after a serialization. +TEST(SerializeNondestructive) { + if (Snapshot::IsEnabled()) return; + StatsTable::SetCounterFunction(counter_function); + v8::HandleScope scope; + Serializer::Enable(); + v8::Persistent<v8::Context> env = v8::Context::New(); + v8::Context::Scope context_scope(env); + Serializer().Serialize(); + const char* c_source = "\"abcd\".charAt(2) == 'c'"; + v8::Local<v8::String> source = v8::String::New(c_source); + v8::Local<v8::Script> script = v8::Script::Compile(source); + v8::Local<v8::Value> value = script->Run(); + CHECK(value->BooleanValue()); +} + +//---------------------------------------------------------------------------- +// Tests that the heap can be deserialized. + +static void Deserialize() { +#ifdef DEBUG + FLAG_debug_serialization = true; +#endif + CHECK(Snapshot::Initialize(FLAG_testing_serialization_file)); +} + + +static void SanityCheck() { + v8::HandleScope scope; +#ifdef DEBUG + Heap::Verify(); +#endif + CHECK(Top::global()->IsJSObject()); + CHECK(Top::global_context()->IsContext()); + CHECK(Top::special_function_table()->IsFixedArray()); + CHECK(Heap::symbol_table()->IsSymbolTable()); + CHECK(!Factory::LookupAsciiSymbol("Empty")->IsFailure()); +} + + +DEPENDENT_TEST(Deserialize, Serialize) { + v8::HandleScope scope; + + Deserialize(); + + SanityCheck(); +} + +DEPENDENT_TEST(DeserializeAndRunScript, Serialize) { + v8::HandleScope scope; + + Deserialize(); + + const char* c_source = "\"1234\".length"; + v8::Local<v8::String> source = v8::String::New(c_source); + v8::Local<v8::Script> script = v8::Script::Compile(source); + CHECK_EQ(4, script->Run()->Int32Value()); +} + + +DEPENDENT_TEST(DeserializeNatives, Serialize) { + v8::HandleScope scope; + + Deserialize(); + + const char* c_source = "\"abcd\".charAt(2) == 'c'"; + v8::Local<v8::String> source = v8::String::New(c_source); + v8::Local<v8::Script> script = v8::Script::Compile(source); + v8::Local<v8::Value> value = script->Run(); + CHECK(value->BooleanValue()); +} + + +DEPENDENT_TEST(DeserializeExtensions, Serialize) { + v8::HandleScope scope; + + Deserialize(); + const char* c_source = "gc();"; + v8::Local<v8::String> source = v8::String::New(c_source); + v8::Local<v8::Script> script = v8::Script::Compile(source); + v8::Local<v8::Value> value = script->Run(); + CHECK(value->IsUndefined()); +} diff --git a/v8/test/cctest/test-sockets.cc b/v8/test/cctest/test-sockets.cc new file mode 100644 index 0000000..a4b2285 --- /dev/null +++ b/v8/test/cctest/test-sockets.cc @@ -0,0 +1,161 @@ +// Copyright 2009 the V8 project authors. All rights reserved. + +#include "v8.h" +#include "platform.h" +#include "cctest.h" + + +using namespace ::v8::internal; + + +class SocketListenerThread : public Thread { + public: + explicit SocketListenerThread(int port, int data_size) + : port_(port), data_size_(data_size), server_(NULL), client_(NULL), + listening_(OS::CreateSemaphore(0)) { + data_ = new char[data_size_]; + } + ~SocketListenerThread() { + // Close both sockets. + delete client_; + delete server_; + delete listening_; + delete[] data_; + } + + void Run(); + void WaitForListening() { listening_->Wait(); } + char* data() { return data_; } + + private: + int port_; + char* data_; + int data_size_; + Socket* server_; // Server socket used for bind/accept. + Socket* client_; // Single client connection used by the test. + Semaphore* listening_; // Signalled when the server socket is in listen mode. +}; + + +void SocketListenerThread::Run() { + bool ok; + + // Create the server socket and bind it to the requested port. + server_ = OS::CreateSocket(); + CHECK(server_ != NULL); + ok = server_->Bind(port_); + CHECK(ok); + + // Listen for new connections. + ok = server_->Listen(1); + CHECK(ok); + listening_->Signal(); + + // Accept a connection. + client_ = server_->Accept(); + CHECK(client_ != NULL); + + // Read the expected niumber of bytes of data. + int bytes_read = 0; + while (bytes_read < data_size_) { + bytes_read += client_->Receive(data_ + bytes_read, data_size_ - bytes_read); + } +} + + +static bool SendAll(Socket* socket, const char* data, int len) { + int sent_len = 0; + while (sent_len < len) { + int status = socket->Send(data, len); + if (status <= 0) { + return false; + } + sent_len += status; + } + return true; +} + + +static void SendAndReceive(int port, char *data, int len) { + static const char* kLocalhost = "localhost"; + + bool ok; + + // Make a string with the port number. + const int kPortBuferLen = 6; + char port_str[kPortBuferLen]; + OS::SNPrintF(Vector<char>(port_str, kPortBuferLen), "%d", port); + + // Create a socket listener. + SocketListenerThread* listener = new SocketListenerThread(port, len); + listener->Start(); + listener->WaitForListening(); + + // Connect and write some data. + Socket* client = OS::CreateSocket(); + CHECK(client != NULL); + ok = client->Connect(kLocalhost, port_str); + CHECK(ok); + + // Send all the data. + ok = SendAll(client, data, len); + CHECK(ok); + + // Wait until data is received. + listener->Join(); + + // Check that data received is the same as data send. + for (int i = 0; i < len; i++) { + CHECK(data[i] == listener->data()[i]); + } + + // Close the client before the listener to avoid TIME_WAIT issues. + client->Shutdown(); + delete client; + delete listener; +} + + +TEST(Socket) { + // Make sure this port is not used by other tests to allow tests to run in + // parallel. + static const int kPort = 5859; + + bool ok; + + // Initialize socket support. + ok = Socket::Setup(); + CHECK(ok); + + // Send and receive some data. + static const int kBufferSizeSmall = 20; + char small_data[kBufferSizeSmall + 1] = "1234567890abcdefghij"; + SendAndReceive(kPort, small_data, kBufferSizeSmall); + + // Send and receive some more data. + static const int kBufferSizeMedium = 10000; + char* medium_data = new char[kBufferSizeMedium]; + for (int i = 0; i < kBufferSizeMedium; i++) { + medium_data[i] = i % 256; + } + SendAndReceive(kPort, medium_data, kBufferSizeMedium); + delete[] medium_data; + + // Send and receive even more data. + static const int kBufferSizeLarge = 1000000; + char* large_data = new char[kBufferSizeLarge]; + for (int i = 0; i < kBufferSizeLarge; i++) { + large_data[i] = i % 256; + } + SendAndReceive(kPort, large_data, kBufferSizeLarge); + delete[] large_data; +} + + +TEST(HToNNToH) { + uint16_t x = 1234; + CHECK_EQ(x, Socket::NToH(Socket::HToN(x))); + + uint32_t y = 12345678; + CHECK(y == Socket::NToH(Socket::HToN(y))); +} diff --git a/v8/test/cctest/test-spaces.cc b/v8/test/cctest/test-spaces.cc new file mode 100644 index 0000000..d946a7f --- /dev/null +++ b/v8/test/cctest/test-spaces.cc @@ -0,0 +1,248 @@ +// Copyright 2006-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" +#include "cctest.h" + +using namespace v8::internal; + +static void VerifyRSet(Address page_start) { +#ifdef DEBUG + Page::set_rset_state(Page::IN_USE); +#endif + + Page* p = Page::FromAddress(page_start); + + p->ClearRSet(); + + for (Address addr = p->ObjectAreaStart(); + addr < p->ObjectAreaEnd(); + addr += kPointerSize) { + CHECK(!Page::IsRSetSet(addr, 0)); + } + + for (Address addr = p->ObjectAreaStart(); + addr < p->ObjectAreaEnd(); + addr += kPointerSize) { + Page::SetRSet(addr, 0); + } + + for (Address addr = p->ObjectAreaStart(); + addr < p->ObjectAreaEnd(); + addr += kPointerSize) { + CHECK(Page::IsRSetSet(addr, 0)); + } +} + + +TEST(Page) { +#ifdef DEBUG + Page::set_rset_state(Page::NOT_IN_USE); +#endif + + byte* mem = NewArray<byte>(2*Page::kPageSize); + CHECK(mem != NULL); + + Address start = reinterpret_cast<Address>(mem); + Address page_start = RoundUp(start, Page::kPageSize); + + Page* p = Page::FromAddress(page_start); + CHECK(p->address() == page_start); + CHECK(p->is_valid()); + + p->opaque_header = 0; + p->is_normal_page = 0x1; + CHECK(!p->next_page()->is_valid()); + + CHECK(p->ObjectAreaStart() == page_start + Page::kObjectStartOffset); + CHECK(p->ObjectAreaEnd() == page_start + Page::kPageSize); + + CHECK(p->Offset(page_start + Page::kObjectStartOffset) == + Page::kObjectStartOffset); + CHECK(p->Offset(page_start + Page::kPageSize) == Page::kPageSize); + + CHECK(p->OffsetToAddress(Page::kObjectStartOffset) == p->ObjectAreaStart()); + CHECK(p->OffsetToAddress(Page::kPageSize) == p->ObjectAreaEnd()); + + // test remember set + VerifyRSet(page_start); + + DeleteArray(mem); +} + + +TEST(MemoryAllocator) { + CHECK(Heap::ConfigureHeapDefault()); + CHECK(MemoryAllocator::Setup(Heap::MaxCapacity())); + + OldSpace faked_space(Heap::MaxCapacity(), OLD_POINTER_SPACE, NOT_EXECUTABLE); + int total_pages = 0; + int requested = 2; + int allocated; + // If we request two pages, we should get one or two. + Page* first_page = + MemoryAllocator::AllocatePages(requested, &allocated, &faked_space); + CHECK(first_page->is_valid()); + CHECK(allocated > 0 && allocated <= 2); + total_pages += allocated; + + Page* last_page = first_page; + for (Page* p = first_page; p->is_valid(); p = p->next_page()) { + CHECK(MemoryAllocator::IsPageInSpace(p, &faked_space)); + last_page = p; + } + + // Again, we should get one or two pages. + Page* others = + MemoryAllocator::AllocatePages(requested, &allocated, &faked_space); + CHECK(others->is_valid()); + CHECK(allocated > 0 && allocated <= 2); + total_pages += allocated; + + MemoryAllocator::SetNextPage(last_page, others); + int page_count = 0; + for (Page* p = first_page; p->is_valid(); p = p->next_page()) { + CHECK(MemoryAllocator::IsPageInSpace(p, &faked_space)); + page_count++; + } + CHECK(total_pages == page_count); + + Page* second_page = first_page->next_page(); + CHECK(second_page->is_valid()); + + // Freeing pages at the first chunk starting at or after the second page + // should free the entire second chunk. It will return the last page in the + // first chunk (if the second page was in the first chunk) or else an + // invalid page (if the second page was the start of the second chunk). + Page* free_return = MemoryAllocator::FreePages(second_page); + CHECK(free_return == last_page || !free_return->is_valid()); + MemoryAllocator::SetNextPage(first_page, free_return); + + // Freeing pages in the first chunk starting at the first page should free + // the first chunk and return an invalid page. + Page* invalid_page = MemoryAllocator::FreePages(first_page); + CHECK(!invalid_page->is_valid()); + + MemoryAllocator::TearDown(); +} + + +TEST(NewSpace) { + CHECK(Heap::ConfigureHeapDefault()); + CHECK(MemoryAllocator::Setup(Heap::MaxCapacity())); + + NewSpace new_space; + + void* chunk = + MemoryAllocator::ReserveInitialChunk(2 * Heap::YoungGenerationSize()); + CHECK(chunk != NULL); + Address start = RoundUp(static_cast<Address>(chunk), + Heap::YoungGenerationSize()); + CHECK(new_space.Setup(start, Heap::YoungGenerationSize())); + CHECK(new_space.HasBeenSetup()); + + while (new_space.Available() >= Page::kMaxHeapObjectSize) { + Object* obj = new_space.AllocateRaw(Page::kMaxHeapObjectSize); + CHECK(!obj->IsFailure()); + CHECK(new_space.Contains(HeapObject::cast(obj))); + } + + new_space.TearDown(); + MemoryAllocator::TearDown(); +} + + +TEST(OldSpace) { + CHECK(Heap::ConfigureHeapDefault()); + CHECK(MemoryAllocator::Setup(Heap::MaxCapacity())); + + OldSpace* s = new OldSpace(Heap::OldGenerationSize(), + OLD_POINTER_SPACE, + NOT_EXECUTABLE); + CHECK(s != NULL); + + void* chunk = + MemoryAllocator::ReserveInitialChunk(2 * Heap::YoungGenerationSize()); + CHECK(chunk != NULL); + Address start = static_cast<Address>(chunk); + size_t size = RoundUp(start, Heap::YoungGenerationSize()) - start; + + CHECK(s->Setup(start, size)); + + while (s->Available() > 0) { + Object* obj = s->AllocateRaw(Page::kMaxHeapObjectSize); + CHECK(!obj->IsFailure()); + } + + s->TearDown(); + delete s; + MemoryAllocator::TearDown(); +} + + +TEST(LargeObjectSpace) { + CHECK(Heap::Setup(false)); + + LargeObjectSpace* lo = Heap::lo_space(); + CHECK(lo != NULL); + + Map* faked_map = reinterpret_cast<Map*>(HeapObject::FromAddress(0)); + int lo_size = Page::kPageSize; + + Object* obj = lo->AllocateRaw(lo_size); + CHECK(!obj->IsFailure()); + CHECK(obj->IsHeapObject()); + + HeapObject* ho = HeapObject::cast(obj); + ho->set_map(faked_map); + + CHECK(lo->Contains(HeapObject::cast(obj))); + + CHECK(lo->FindObject(ho->address()) == obj); + + CHECK(lo->Contains(ho)); + + while (true) { + int available = lo->Available(); + obj = lo->AllocateRaw(lo_size); + if (obj->IsFailure()) break; + HeapObject::cast(obj)->set_map(faked_map); + CHECK(lo->Available() < available); + }; + + CHECK(!lo->IsEmpty()); + + obj = lo->AllocateRaw(lo_size); + CHECK(obj->IsFailure()); + + lo->TearDown(); + delete lo; + + MemoryAllocator::TearDown(); +} diff --git a/v8/test/cctest/test-strings.cc b/v8/test/cctest/test-strings.cc new file mode 100644 index 0000000..b38d645 --- /dev/null +++ b/v8/test/cctest/test-strings.cc @@ -0,0 +1,389 @@ +// Copyright 2006-2008 the V8 project authors. All rights reserved. + +// Check that we can traverse very deep stacks of ConsStrings using +// StringInputBuffer. Check that Get(int) works on very deep stacks +// of ConsStrings. These operations may not be very fast, but they +// should be possible without getting errors due to too deep recursion. + +#include <stdlib.h> + +#include "v8.h" + +#include "factory.h" +#include "cctest.h" +#include "zone-inl.h" + +unsigned int seed = 123; + +static uint32_t gen() { + uint64_t z; + z = seed; + z *= 279470273; + z %= 4294967291U; + seed = static_cast<unsigned int>(z); + return static_cast<uint32_t>(seed >> 16); +} + + +using namespace v8::internal; + +static v8::Persistent<v8::Context> env; + + +static void InitializeVM() { + if (env.IsEmpty()) { + v8::HandleScope scope; + const char* extensions[] = { "v8/print" }; + v8::ExtensionConfiguration config(1, extensions); + env = v8::Context::New(&config); + } + v8::HandleScope scope; + env->Enter(); +} + + +static const int NUMBER_OF_BUILDING_BLOCKS = 128; +static const int DEEP_DEPTH = 8 * 1024; +static const int SUPER_DEEP_DEPTH = 80 * 1024; + + +static void InitializeBuildingBlocks( + Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS]) { + // A list of pointers that we don't have any interest in cleaning up. + // If they are reachable from a root then leak detection won't complain. + for (int i = 0; i < NUMBER_OF_BUILDING_BLOCKS; i++) { + int len = gen() % 16; + if (len > 14) { + len += 1234; + } + switch (gen() % 4) { + case 0: { + uc16 buf[2000]; + for (int j = 0; j < len; j++) { + buf[j] = gen() % 65536; + } + building_blocks[i] = + Factory::NewStringFromTwoByte(Vector<const uc16>(buf, len)); + for (int j = 0; j < len; j++) { + CHECK_EQ(buf[j], building_blocks[i]->Get(j)); + } + break; + } + case 1: { + char buf[2000]; + for (int j = 0; j < len; j++) { + buf[j] = gen() % 128; + } + building_blocks[i] = + Factory::NewStringFromAscii(Vector<const char>(buf, len)); + for (int j = 0; j < len; j++) { + CHECK_EQ(buf[j], building_blocks[i]->Get(j)); + } + break; + } + case 2: { + class Resource: public v8::String::ExternalStringResource, + public ZoneObject { + public: + explicit Resource(Vector<const uc16> string): data_(string.start()) { + length_ = string.length(); + } + virtual const uint16_t* data() const { return data_; } + virtual size_t length() const { return length_; } + + private: + const uc16* data_; + size_t length_; + }; + uc16* buf = Zone::NewArray<uc16>(len); + for (int j = 0; j < len; j++) { + buf[j] = gen() % 65536; + } + Resource* resource = new Resource(Vector<const uc16>(buf, len)); + building_blocks[i] = Factory::NewExternalStringFromTwoByte(resource); + for (int j = 0; j < len; j++) { + CHECK_EQ(buf[j], building_blocks[i]->Get(j)); + } + break; + } + case 3: { + char* buf = NewArray<char>(len); + for (int j = 0; j < len; j++) { + buf[j] = gen() % 128; + } + building_blocks[i] = + Factory::NewStringFromAscii(Vector<const char>(buf, len)); + for (int j = 0; j < len; j++) { + CHECK_EQ(buf[j], building_blocks[i]->Get(j)); + } + DeleteArray<char>(buf); + break; + } + } + } +} + + +static Handle<String> ConstructLeft( + Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS], + int depth) { + Handle<String> answer = Factory::NewStringFromAscii(CStrVector("")); + for (int i = 0; i < depth; i++) { + answer = Factory::NewConsString( + answer, + building_blocks[i % NUMBER_OF_BUILDING_BLOCKS]); + } + return answer; +} + + +static Handle<String> ConstructRight( + Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS], + int depth) { + Handle<String> answer = Factory::NewStringFromAscii(CStrVector("")); + for (int i = depth - 1; i >= 0; i--) { + answer = Factory::NewConsString( + building_blocks[i % NUMBER_OF_BUILDING_BLOCKS], + answer); + } + return answer; +} + + +static Handle<String> ConstructBalancedHelper( + Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS], + int from, + int to) { + ASSERT(to > from); + if (to - from == 1) { + return building_blocks[from % NUMBER_OF_BUILDING_BLOCKS]; + } + if (to - from == 2) { + return Factory::NewConsString( + building_blocks[from % NUMBER_OF_BUILDING_BLOCKS], + building_blocks[(from+1) % NUMBER_OF_BUILDING_BLOCKS]); + } + Handle<String> part1 = + ConstructBalancedHelper(building_blocks, from, from + ((to - from) / 2)); + Handle<String> part2 = + ConstructBalancedHelper(building_blocks, from + ((to - from) / 2), to); + return Factory::NewConsString(part1, part2); +} + + +static Handle<String> ConstructBalanced( + Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS]) { + return ConstructBalancedHelper(building_blocks, 0, DEEP_DEPTH); +} + + +static StringInputBuffer buffer; + + +static void Traverse(Handle<String> s1, Handle<String> s2) { + int i = 0; + buffer.Reset(*s1); + StringInputBuffer buffer2(*s2); + while (buffer.has_more()) { + CHECK(buffer2.has_more()); + uint16_t c = buffer.GetNext(); + CHECK_EQ(c, buffer2.GetNext()); + i++; + } + CHECK_EQ(s1->length(), i); + CHECK_EQ(s2->length(), i); +} + + +static void TraverseFirst(Handle<String> s1, Handle<String> s2, int chars) { + int i = 0; + buffer.Reset(*s1); + StringInputBuffer buffer2(*s2); + while (buffer.has_more() && i < chars) { + CHECK(buffer2.has_more()); + uint16_t c = buffer.GetNext(); + CHECK_EQ(c, buffer2.GetNext()); + i++; + } + s1->Get(s1->length() - 1); + s2->Get(s2->length() - 1); +} + + +TEST(Traverse) { + printf("TestTraverse\n"); + InitializeVM(); + v8::HandleScope scope; + Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS]; + ZoneScope zone(DELETE_ON_EXIT); + InitializeBuildingBlocks(building_blocks); + Handle<String> flat = ConstructBalanced(building_blocks); + FlattenString(flat); + Handle<String> left_asymmetric = ConstructLeft(building_blocks, DEEP_DEPTH); + Handle<String> right_asymmetric = ConstructRight(building_blocks, DEEP_DEPTH); + Handle<String> symmetric = ConstructBalanced(building_blocks); + printf("1\n"); + Traverse(flat, symmetric); + printf("2\n"); + Traverse(flat, left_asymmetric); + printf("3\n"); + Traverse(flat, right_asymmetric); + printf("4\n"); + Handle<String> left_deep_asymmetric = + ConstructLeft(building_blocks, SUPER_DEEP_DEPTH); + Handle<String> right_deep_asymmetric = + ConstructRight(building_blocks, SUPER_DEEP_DEPTH); + printf("5\n"); + TraverseFirst(left_asymmetric, left_deep_asymmetric, 1050); + printf("6\n"); + TraverseFirst(left_asymmetric, right_deep_asymmetric, 65536); + printf("7\n"); + Handle<String> right_deep_slice = + Factory::NewStringSlice(left_deep_asymmetric, + left_deep_asymmetric->length() - 1050, + left_deep_asymmetric->length() - 50); + Handle<String> left_deep_slice = + Factory::NewStringSlice(right_deep_asymmetric, + right_deep_asymmetric->length() - 1050, + right_deep_asymmetric->length() - 50); + printf("8\n"); + Traverse(right_deep_slice, left_deep_slice); + printf("9\n"); + FlattenString(left_asymmetric); + printf("10\n"); + Traverse(flat, left_asymmetric); + printf("11\n"); + FlattenString(right_asymmetric); + printf("12\n"); + Traverse(flat, right_asymmetric); + printf("14\n"); + FlattenString(symmetric); + printf("15\n"); + Traverse(flat, symmetric); + printf("16\n"); + FlattenString(left_deep_asymmetric); + printf("18\n"); +} + + +static Handle<String> SliceOf(Handle<String> underlying) { + int start = gen() % underlying->length(); + int end = start + gen() % (underlying->length() - start); + return Factory::NewStringSlice(underlying, + start, + end); +} + + +static Handle<String> ConstructSliceTree( + Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS], + int from, + int to) { + ASSERT(to > from); + if (to - from <= 1) + return SliceOf(building_blocks[from % NUMBER_OF_BUILDING_BLOCKS]); + if (to - from == 2) { + Handle<String> lhs = building_blocks[from % NUMBER_OF_BUILDING_BLOCKS]; + if (gen() % 2 == 0) + lhs = SliceOf(lhs); + Handle<String> rhs = building_blocks[(from+1) % NUMBER_OF_BUILDING_BLOCKS]; + if (gen() % 2 == 0) + rhs = SliceOf(rhs); + return Factory::NewConsString(lhs, rhs); + } + Handle<String> part1 = + ConstructBalancedHelper(building_blocks, from, from + ((to - from) / 2)); + Handle<String> part2 = + ConstructBalancedHelper(building_blocks, from + ((to - from) / 2), to); + Handle<String> branch = Factory::NewConsString(part1, part2); + if (gen() % 2 == 0) + return branch; + return(SliceOf(branch)); +} + + +TEST(Slice) { + printf("TestSlice\n"); + InitializeVM(); + v8::HandleScope scope; + Handle<String> building_blocks[NUMBER_OF_BUILDING_BLOCKS]; + ZoneScope zone(DELETE_ON_EXIT); + InitializeBuildingBlocks(building_blocks); + + seed = 42; + Handle<String> slice_tree = + ConstructSliceTree(building_blocks, 0, DEEP_DEPTH); + seed = 42; + Handle<String> flat_slice_tree = + ConstructSliceTree(building_blocks, 0, DEEP_DEPTH); + FlattenString(flat_slice_tree); + Traverse(flat_slice_tree, slice_tree); +} + +static const int DEEP_ASCII_DEPTH = 100000; + + +TEST(DeepAscii) { + printf("TestDeepAscii\n"); + InitializeVM(); + v8::HandleScope scope; + + char* foo = NewArray<char>(DEEP_ASCII_DEPTH); + for (int i = 0; i < DEEP_ASCII_DEPTH; i++) { + foo[i] = "foo "[i % 4]; + } + Handle<String> string = + Factory::NewStringFromAscii(Vector<const char>(foo, DEEP_ASCII_DEPTH)); + Handle<String> foo_string = Factory::NewStringFromAscii(CStrVector("foo")); + for (int i = 0; i < DEEP_ASCII_DEPTH; i += 10) { + string = Factory::NewConsString(string, foo_string); + } + Handle<String> flat_string = Factory::NewConsString(string, foo_string); + FlattenString(flat_string); + + for (int i = 0; i < 500; i++) { + TraverseFirst(flat_string, string, DEEP_ASCII_DEPTH); + } + DeleteArray<char>(foo); +} + + +TEST(Utf8Conversion) { + // Smoke test for converting strings to utf-8. + InitializeVM(); + v8::HandleScope handle_scope; + // A simple ascii string + const char* ascii_string = "abcdef12345"; + int len = v8::String::New(ascii_string, strlen(ascii_string))->Utf8Length(); + CHECK_EQ(strlen(ascii_string), len); + // A mixed ascii and non-ascii string + // U+02E4 -> CB A4 + // U+0064 -> 64 + // U+12E4 -> E1 8B A4 + // U+0030 -> 30 + // U+3045 -> E3 81 85 + const uint16_t mixed_string[] = {0x02E4, 0x0064, 0x12E4, 0x0030, 0x3045}; + // The characters we expect to be output + const unsigned char as_utf8[11] = {0xCB, 0xA4, 0x64, 0xE1, 0x8B, 0xA4, 0x30, + 0xE3, 0x81, 0x85, 0x00}; + // The number of bytes expected to be written for each length + const int lengths[12] = {0, 0, 2, 3, 3, 3, 6, 7, 7, 7, 10, 11}; + v8::Handle<v8::String> mixed = v8::String::New(mixed_string, 5); + CHECK_EQ(10, mixed->Utf8Length()); + // Try encoding the string with all capacities + char buffer[11]; + const char kNoChar = static_cast<char>(-1); + for (int i = 0; i <= 11; i++) { + // Clear the buffer before reusing it + for (int j = 0; j < 11; j++) + buffer[j] = kNoChar; + int written = mixed->WriteUtf8(buffer, i); + CHECK_EQ(lengths[i], written); + // Check that the contents are correct + for (int j = 0; j < lengths[i]; j++) + CHECK_EQ(as_utf8[j], static_cast<unsigned char>(buffer[j])); + // Check that the rest of the buffer hasn't been touched + for (int j = lengths[i]; j < 11; j++) + CHECK_EQ(kNoChar, buffer[j]); + } +} diff --git a/v8/test/cctest/test-threads.cc b/v8/test/cctest/test-threads.cc new file mode 100644 index 0000000..c0d55f2 --- /dev/null +++ b/v8/test/cctest/test-threads.cc @@ -0,0 +1,54 @@ +// Copyright 2008 the V8 project authors. 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. + +#include "v8.h" + +#include "platform.h" + +#include "cctest.h" + + +TEST(Preemption) { + v8::Locker locker; + v8::V8::Initialize(); + v8::HandleScope scope; + v8::Context::Scope context_scope(v8::Context::New()); + + v8::Locker::StartPreemption(100); + + v8::Handle<v8::Script> script = v8::Script::Compile( + v8::String::New("var count = 0; var obj = new Object(); count++;\n")); + + script->Run(); + + v8::Locker::StopPreemption(); + v8::internal::OS::Sleep(500); // Make sure the timer fires. + + script->Run(); +} + + diff --git a/v8/test/cctest/test-utils.cc b/v8/test/cctest/test-utils.cc new file mode 100644 index 0000000..acb5d3c --- /dev/null +++ b/v8/test/cctest/test-utils.cc @@ -0,0 +1,179 @@ +// Copyright 2006-2008 the V8 project authors. 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. + +#include <stdlib.h> + +#include "v8.h" + +#include "platform.h" +#include "cctest.h" + +using namespace v8::internal; + + +enum Mode { + forward, + backward_unsigned +}; + + +static v8::internal::byte* Write(v8::internal::byte* p, Mode m, int x) { + v8::internal::byte* q = NULL; + switch (m) { + case forward: + q = EncodeInt(p, x); + CHECK(q <= p + sizeof(x) + 1); + break; + case backward_unsigned: + q = EncodeUnsignedIntBackward(p, x); + CHECK(q >= p - sizeof(x) - 1); + break; + } + return q; +} + + +static v8::internal::byte* Read(v8::internal::byte* p, Mode m, int x) { + v8::internal::byte* q = NULL; + int y; + switch (m) { + case forward: + q = DecodeInt(p, &y); + CHECK(q <= p + sizeof(y) + 1); + break; + case backward_unsigned: { + unsigned int uy; + q = DecodeUnsignedIntBackward(p, &uy); + y = uy; + CHECK(q >= p - sizeof(uy) - 1); + break; + } + } + CHECK(y == x); + return q; +} + + +static v8::internal::byte* WriteMany(v8::internal::byte* p, Mode m, int x) { + p = Write(p, m, x - 7); + p = Write(p, m, x - 1); + p = Write(p, m, x); + p = Write(p, m, x + 1); + p = Write(p, m, x + 2); + p = Write(p, m, -x - 5); + p = Write(p, m, -x - 1); + p = Write(p, m, -x); + p = Write(p, m, -x + 1); + p = Write(p, m, -x + 3); + + return p; +} + + +static v8::internal::byte* ReadMany(v8::internal::byte* p, Mode m, int x) { + p = Read(p, m, x - 7); + p = Read(p, m, x - 1); + p = Read(p, m, x); + p = Read(p, m, x + 1); + p = Read(p, m, x + 2); + p = Read(p, m, -x - 5); + p = Read(p, m, -x - 1); + p = Read(p, m, -x); + p = Read(p, m, -x + 1); + p = Read(p, m, -x + 3); + + return p; +} + + +void ProcessValues(int* values, int n, Mode m) { + v8::internal::byte buf[4 * KB]; // make this big enough + v8::internal::byte* p0 = (m == forward ? buf : buf + ARRAY_SIZE(buf)); + + v8::internal::byte* p = p0; + for (int i = 0; i < n; i++) { + p = WriteMany(p, m, values[i]); + } + + v8::internal::byte* q = p0; + for (int i = 0; i < n; i++) { + q = ReadMany(q, m, values[i]); + } + + CHECK(p == q); +} + + +TEST(Utils0) { + int values[] = { + 0, 1, 10, 16, 32, 64, 128, 256, 512, 1024, 1234, 5731, + 10000, 100000, 1000000, 10000000, 100000000, 1000000000 + }; + const int n = ARRAY_SIZE(values); + + ProcessValues(values, n, forward); + ProcessValues(values, n, backward_unsigned); +} + + +TEST(Utils1) { + CHECK_EQ(-1000000, FastD2I(-1000000.0)); + CHECK_EQ(-1, FastD2I(-1.0)); + CHECK_EQ(0, FastD2I(0.0)); + CHECK_EQ(1, FastD2I(1.0)); + CHECK_EQ(1000000, FastD2I(1000000.0)); + + CHECK_EQ(-1000000, FastD2I(-1000000.123)); + CHECK_EQ(-1, FastD2I(-1.234)); + CHECK_EQ(0, FastD2I(0.345)); + CHECK_EQ(1, FastD2I(1.234)); + CHECK_EQ(1000000, FastD2I(1000000.123)); +} + + +TEST(SNPrintF) { + // Make sure that strings that are truncated because of too small + // buffers are zero-terminated anyway. + const char* s = "the quick lazy .... oh forget it!"; + int length = strlen(s); + for (int i = 1; i < length * 2; i++) { + static const char kMarker = static_cast<char>(42); + Vector<char> buffer = Vector<char>::New(i + 1); + buffer[i] = kMarker; + int n = OS::SNPrintF(Vector<char>(buffer.start(), i), "%s", s); + CHECK(n <= i); + CHECK(n == length || n == -1); + CHECK_EQ(0, strncmp(buffer.start(), s, i - 1)); + CHECK_EQ(kMarker, buffer[i]); + if (i <= length) { + CHECK_EQ(i - 1, strlen(buffer.start())); + } else { + CHECK_EQ(length, strlen(buffer.start())); + } + buffer.Dispose(); + } +} diff --git a/v8/test/cctest/testcfg.py b/v8/test/cctest/testcfg.py new file mode 100644 index 0000000..75377db --- /dev/null +++ b/v8/test/cctest/testcfg.py @@ -0,0 +1,108 @@ +# Copyright 2008 the V8 project authors. 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. + +import test +import os +from os.path import join, dirname, exists +import platform +import utils + +DEBUG_FLAGS = ['--enable-slow-asserts', '--debug-code', '--verify-heap'] + + +class CcTestCase(test.TestCase): + + def __init__(self, path, executable, mode, raw_name, dependency, context): + super(CcTestCase, self).__init__(context, path) + self.executable = executable + self.mode = mode + self.raw_name = raw_name + self.dependency = dependency + + def GetLabel(self): + return "%s %s %s" % (self.mode, self.path[-2], self.path[-1]) + + def GetName(self): + return self.path[-1] + + def BuildCommand(self, name): + serialization_file = join('obj', 'test', self.mode, 'serdes') + serialization_file += '_' + self.GetName() + serialization_option = '--testing_serialization_file=' + serialization_file + result = [ self.executable, name, serialization_option ] + if self.mode == 'debug': + result += DEBUG_FLAGS + return result + + def GetCommand(self): + return self.BuildCommand(self.raw_name) + + def Run(self): + if self.dependency != '': + dependent_command = self.BuildCommand(self.dependency) + output = self.RunCommand(dependent_command) + if output.HasFailed(): + return output + return test.TestCase.Run(self) + + +class CcTestConfiguration(test.TestConfiguration): + + def __init__(self, context, root): + super(CcTestConfiguration, self).__init__(context, root) + + def GetBuildRequirements(self): + return ['cctests'] + + def ListTests(self, current_path, path, mode): + executable = join('obj', 'test', mode, 'cctest') + if utils.IsWindows(): + executable += '.exe' + output = test.Execute([executable, '--list'], self.context) + if output.exit_code != 0: + print output.stdout + print output.stderr + return [] + result = [] + for test_desc in output.stdout.strip().split(): + raw_test, dependency = test_desc.split('<') + relative_path = raw_test.split('/') + full_path = current_path + relative_path + if dependency != '': + dependency = relative_path[0] + '/' + dependency + if self.Contains(path, full_path): + result.append(CcTestCase(full_path, executable, mode, raw_test, dependency, self.context)) + return result + + def GetTestStatus(self, sections, defs): + status_file = join(self.root, 'cctest.status') + if exists(status_file): + test.ReadConfigurationInto(status_file, sections, defs) + + +def GetConfiguration(context, root): + return CcTestConfiguration(context, root) |