// Copyright 2016 the V8 project 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 "src/builtins/builtins-utils-inl.h"
#include "src/builtins/builtins.h"
#include "src/heap/heap-inl.h"  // For ToBoolean.
#include "src/logging/counters.h"
#include "src/objects/call-site-info-inl.h"
#include "src/objects/objects-inl.h"

namespace v8 {
namespace internal {

#define CHECK_CALLSITE(frame, method)                                         \
  CHECK_RECEIVER(JSObject, receiver, method);                                 \
  LookupIterator it(isolate, receiver,                                        \
                    isolate->factory()->call_site_info_symbol(),              \
                    LookupIterator::OWN_SKIP_INTERCEPTOR);                    \
  if (it.state() != LookupIterator::DATA) {                                   \
    THROW_NEW_ERROR_RETURN_FAILURE(                                           \
        isolate,                                                              \
        NewTypeError(MessageTemplate::kCallSiteMethod,                        \
                     isolate->factory()->NewStringFromAsciiChecked(method))); \
  }                                                                           \
  Handle<CallSiteInfo> frame = Handle<CallSiteInfo>::cast(it.GetDataValue())

namespace {

Object PositiveNumberOrNull(int value, Isolate* isolate) {
  if (value > 0) return *isolate->factory()->NewNumberFromInt(value);
  return ReadOnlyRoots(isolate).null_value();
}

bool NativeContextIsForShadowRealm(NativeContext native_context) {
  return native_context.scope_info().scope_type() == SHADOW_REALM_SCOPE;
}

}  // namespace

BUILTIN(CallSitePrototypeGetColumnNumber) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getColumnNumber");
  return PositiveNumberOrNull(CallSiteInfo::GetColumnNumber(frame), isolate);
}

BUILTIN(CallSitePrototypeGetEnclosingColumnNumber) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getEnclosingColumnNumber");
  return PositiveNumberOrNull(CallSiteInfo::GetEnclosingColumnNumber(frame),
                              isolate);
}

BUILTIN(CallSitePrototypeGetEnclosingLineNumber) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getEnclosingLineNumber");
  return PositiveNumberOrNull(CallSiteInfo::GetEnclosingLineNumber(frame),
                              isolate);
}

BUILTIN(CallSitePrototypeGetEvalOrigin) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getEvalOrigin");
  return *CallSiteInfo::GetEvalOrigin(frame);
}

BUILTIN(CallSitePrototypeGetFileName) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getFileName");
  return frame->GetScriptName();
}

BUILTIN(CallSitePrototypeGetFunction) {
  static const char method_name[] = "getFunction";
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, method_name);
  // ShadowRealms have a boundary: references to outside objects must not exist
  // in the ShadowRealm, and references to ShadowRealm objects must not exist
  // outside the ShadowRealm.
  if (NativeContextIsForShadowRealm(isolate->raw_native_context()) ||
      (frame->function().IsJSFunction() &&
       NativeContextIsForShadowRealm(
           JSFunction::cast(frame->function()).native_context()))) {
    THROW_NEW_ERROR_RETURN_FAILURE(
        isolate,
        NewTypeError(
            MessageTemplate::kCallSiteMethodUnsupportedInShadowRealm,
            isolate->factory()->NewStringFromAsciiChecked(method_name)));
  }
  if (frame->IsStrict() ||
      (frame->function().IsJSFunction() &&
       JSFunction::cast(frame->function()).shared().is_toplevel())) {
    return ReadOnlyRoots(isolate).undefined_value();
  }
  isolate->CountUsage(v8::Isolate::kCallSiteAPIGetFunctionSloppyCall);
  return frame->function();
}

BUILTIN(CallSitePrototypeGetFunctionName) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getFunctionName");
  return *CallSiteInfo::GetFunctionName(frame);
}

BUILTIN(CallSitePrototypeGetLineNumber) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getLineNumber");
  return PositiveNumberOrNull(CallSiteInfo::GetLineNumber(frame), isolate);
}

BUILTIN(CallSitePrototypeGetMethodName) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getMethodName");
  return *CallSiteInfo::GetMethodName(frame);
}

BUILTIN(CallSitePrototypeGetPosition) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getPosition");
  return Smi::FromInt(CallSiteInfo::GetSourcePosition(frame));
}

BUILTIN(CallSitePrototypeGetPromiseIndex) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getPromiseIndex");
  if (!frame->IsPromiseAll() && !frame->IsPromiseAny() &&
      !frame->IsPromiseAllSettled()) {
    return ReadOnlyRoots(isolate).null_value();
  }
  return Smi::FromInt(CallSiteInfo::GetSourcePosition(frame));
}

BUILTIN(CallSitePrototypeGetScriptHash) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getScriptHash");
  return *CallSiteInfo::GetScriptHash(frame);
}

BUILTIN(CallSitePrototypeGetScriptNameOrSourceURL) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getScriptNameOrSourceUrl");
  return frame->GetScriptNameOrSourceURL();
}

BUILTIN(CallSitePrototypeGetThis) {
  static const char method_name[] = "getThis";
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, method_name);
  // ShadowRealms have a boundary: references to outside objects must not exist
  // in the ShadowRealm, and references to ShadowRealm objects must not exist
  // outside the ShadowRealm.
  if (NativeContextIsForShadowRealm(isolate->raw_native_context()) ||
      (frame->function().IsJSFunction() &&
       NativeContextIsForShadowRealm(
           JSFunction::cast(frame->function()).native_context()))) {
    THROW_NEW_ERROR_RETURN_FAILURE(
        isolate,
        NewTypeError(
            MessageTemplate::kCallSiteMethodUnsupportedInShadowRealm,
            isolate->factory()->NewStringFromAsciiChecked(method_name)));
  }
  if (frame->IsStrict()) return ReadOnlyRoots(isolate).undefined_value();
  isolate->CountUsage(v8::Isolate::kCallSiteAPIGetThisSloppyCall);
#if V8_ENABLE_WEBASSEMBLY
  if (frame->IsAsmJsWasm()) {
    return frame->GetWasmInstance().native_context().global_proxy();
  }
#endif  // V8_ENABLE_WEBASSEMBLY
  return frame->receiver_or_instance();
}

BUILTIN(CallSitePrototypeGetTypeName) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "getTypeName");
  return *CallSiteInfo::GetTypeName(frame);
}

BUILTIN(CallSitePrototypeIsAsync) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "isAsync");
  return isolate->heap()->ToBoolean(frame->IsAsync());
}

BUILTIN(CallSitePrototypeIsConstructor) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "isConstructor");
  return isolate->heap()->ToBoolean(frame->IsConstructor());
}

BUILTIN(CallSitePrototypeIsEval) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "isEval");
  return isolate->heap()->ToBoolean(frame->IsEval());
}

BUILTIN(CallSitePrototypeIsNative) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "isNative");
  return isolate->heap()->ToBoolean(frame->IsNative());
}

BUILTIN(CallSitePrototypeIsPromiseAll) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "isPromiseAll");
  return isolate->heap()->ToBoolean(frame->IsPromiseAll());
}

BUILTIN(CallSitePrototypeIsToplevel) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "isToplevel");
  return isolate->heap()->ToBoolean(frame->IsToplevel());
}

BUILTIN(CallSitePrototypeToString) {
  HandleScope scope(isolate);
  CHECK_CALLSITE(frame, "toString");
  RETURN_RESULT_OR_FAILURE(isolate, SerializeCallSiteInfo(isolate, frame));
}

#undef CHECK_CALLSITE

}  // namespace internal
}  // namespace v8
