/*
 * Copyright (c) 2013 The Native Client Authors. All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include <stddef.h>
#include <string.h>

#include "native_client/src/include/nacl_assert.h"


// This test checks the representation of C++ method pointers used by
// the C++ front end.

// Define example classes where a method pointer's this-offset and
// vtable-offset are both non-zero.

class TestBaseClass1 {
 public:
  virtual ~TestBaseClass1() {}
  virtual void some_virtual() {}
  char data[1000];
};

class TestBaseClass2 {
 public:
  virtual ~TestBaseClass2() {}
  int nonvirtual_method() { return 123; }
  virtual void other_method1() {}
  virtual void other_method2() {}
  virtual int virtual_method() { return 456; }
};

class TestClass : public TestBaseClass1, public TestBaseClass2 {
};

// Offset of virtual_method() in vtable: it comes after
// ~TestBaseClass1(), ~TestBaseClass2(), other_method1() and
// other_method2().
const int kVtableOffset = sizeof(void *) * 4;
const int kThisOffset = sizeof(TestBaseClass1);

// Mangled name for TestBaseClass2::method()
#define NONVIRTUAL_METHOD _ZN14TestBaseClass217nonvirtual_methodEv
extern "C" void NONVIRTUAL_METHOD();


typedef int (TestClass::*method_ptr)();

struct method_ptr_repr {
  ptrdiff_t ptr;
  ptrdiff_t adj;
};


int main() {
  ASSERT_EQ(sizeof(method_ptr), 8);
  ASSERT_EQ(kThisOffset, 1004);

  method_ptr nonvirtual_mp = &TestClass::nonvirtual_method;
  method_ptr virtual_mp = &TestClass::virtual_method;

  // Check that method pointers work.
  TestClass instance;
  ASSERT_EQ((instance.*nonvirtual_mp)(), 123);
  ASSERT_EQ((instance.*virtual_mp)(), 456);

  method_ptr_repr nonvirtual_mpr;
  method_ptr_repr virtual_mpr;
  // Copy to avoid strict-aliasing problems.
  memcpy(&nonvirtual_mpr, &nonvirtual_mp, sizeof(method_ptr));
  memcpy(&virtual_mpr, &virtual_mp, sizeof(method_ptr));

#if defined(__arm__) || defined(__pnacl__)
  // In the ARM scheme, adj & 1 indicates whether the method is
  // virtual.  This makes no assumption about the alignment of
  // function pointers.  PNaCl uses the same scheme (see
  // https://code.google.com/p/nativeclient/issues/detail?id=3450).

  ASSERT_EQ(nonvirtual_mpr.ptr, (ptrdiff_t) NONVIRTUAL_METHOD);
  ASSERT_EQ(nonvirtual_mpr.adj, kThisOffset << 1);

  ASSERT_EQ(virtual_mpr.ptr, kVtableOffset);
  ASSERT_EQ(virtual_mpr.adj, (kThisOffset << 1) + 1);
#else
  // In the Itanium scheme (used for x86), ptr & 1 indicates whether
  // the method is virtual.  This assumes that function pointers are 0
  // mod 2, so we do not want to use this scheme for PNaCl.

  ASSERT_EQ(nonvirtual_mpr.ptr, (ptrdiff_t) NONVIRTUAL_METHOD);
  ASSERT_EQ(nonvirtual_mpr.adj, kThisOffset);

  ASSERT_EQ(virtual_mpr.ptr, kVtableOffset + 1);
  ASSERT_EQ(virtual_mpr.adj, kThisOffset);
#endif

  return 0;
}
