1
    2
    3
    4
    5
    6
    7
    8
    9
   10
   11
   12
   13
   14
   15
   16
   17
   18
   19
   20
   21
   22
   23
   24
   25
   26
   27
   28
   29
   30
   31
   32
   33
   34
   35
   36
   37
   38
   39
   40
   41
   42
   43
   44
   45
   46
   47
   48
   49
   50
   51
   52
   53
   54
   55
   56
   57
   58
   59
   60
   61
   62
   63
   64
   65
   66
   67
   68
   69
   70
   71
   72
   73
   74
   75
   76
   77
   78
   79
   80
   81
   82
   83
   84
   85
   86
   87
   88
   89
   90
   91
   92
   93
   94
   95
   96
   97
   98
   99
  100
  101
  102
  103
  104
  105
  106
  107
  108
  109
  110
  111
  112
  113
  114
  115
  116
  117
  118
  119
  120
  121
  122
  123
  124
  125
  126
  127
  128
  129
  130
  131
  132
  133
  134
  135
  136
  137
  138
  139
  140
  141
  142
  143
  144
  145
  146
  147
  148
  149
  150
  151
  152
  153
  154
  155
  156
  157
  158
  159
  160
  161
  162
  163
  164
  165
  166
  167
  168
  169
  170
  171
  172
  173
  174
  175
  176
  177
  178
  179
  180
  181
  182
  183
  184
  185
  186
  187
  188
  189
  190
  191
  192
  193
  194
  195
  196
  197
  198
  199
  200
  201
  202
  203
  204
  205
  206
  207
  208
  209
  210
  211
  212
  213
  214
  215
  216
  217
  218
  219
  220
  221
  222
  223
  224
  225
  226
  227
  228
  229
  230
  231
  232
  233
  234
  235
  236
  237
  238
  239
  240
  241
  242
  243
  244
  245
  246
  247
  248
  249
  250
  251
  252
  253
  254
  255
  256
  257
  258
  259
  260
  261
  262
  263
  264
  265
  266
  267
  268
  269
  270
  271
  272
  273
  274
  275
  276
  277
  278
  279
  280
  281
  282
  283
  284
  285
  286
  287
  288
  289
  290
  291
  292
  293
  294
  295
  296
  297
  298
  299
  300
  301
  302
  303
  304
  305
  306
  307
  308
  309
  310
  311
  312
  313
  314
  315
  316
  317
  318
  319
  320
  321
  322
  323
  324
  325
  326
  327
  328
  329
  330
  331
  332
  333
  334
  335
  336
  337
  338
  339
  340
  341
  342
  343
  344
  345
  346
  347
  348
  349
  350
  351
  352
  353
  354
  355
  356
  357
  358
  359
  360
  361
  362
  363
  364
  365
  366
  367
  368
  369
  370
  371
  372
  373
  374
  375
  376
  377
  378
  379
  380
  381
  382
  383
  384
  385
  386
  387
  388
  389
  390
  391
  392
  393
  394
  395
  396
  397
  398
  399
  400
  401
  402
  403
  404
  405
  406
  407
  408
  409
  410
  411
  412
  413
  414
  415
  416
  417
  418
  419
  420
  421
  422
  423
  424
  425
  426
  427
  428

content / services / auction_worklet / webidl_compat.h [blame]

// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CONTENT_SERVICES_AUCTION_WORKLET_WEBIDL_COMPAT_H_
#define CONTENT_SERVICES_AUCTION_WORKLET_WEBIDL_COMPAT_H_

#include <initializer_list>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "content/common/content_export.h"
#include "content/services/auction_worklet/auction_v8_helper.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "v8/include/v8-local-handle.h"
#include "v8/include/v8-value.h"

namespace v8 {
class TryCatch;
class Isolate;
}  // namespace v8

namespace auction_worklet {

// Helps distinguish between double and unrestricted double in IDL.
struct CONTENT_EXPORT UnrestrictedDouble {
  double number;
};

// IdlConvert converts v8::Value values to a C++ representation
// following the semantics of WebIDL for certain simpler, non-structured,
// WebIDL types. The IDL type desired is denoted by the output parameter of the
// particular Convert() overload used.
//
// Clients should have AuctionV8Helper::TimeLimitScope active to protect against
// non-termination.
//
// `error_prefix` is prepended to non-exception error messages,
//  in order to identify where the error occurred, e.g. foo.js:100, etc.
//
// `error_subject` names the thing being converted (e.g. field foo,
//  argument bar, etc.)
class CONTENT_EXPORT IdlConvert {
 public:
  // This should be bigger than the biggest Sequence<> the users of this need to
  // handle, which is currently up to
  // blink::kMaxAdAuctionAdComponentsConfigLimit.
  static const size_t kSequenceLengthLimit = 101;

  // Outcome of the conversion, either success or some kind of error.
  class CONTENT_EXPORT Status {
   public:
    enum class Success { kSuccessTag };

    struct CONTENT_EXPORT Timeout {
      std::string timeout_message;
    };

    struct CONTENT_EXPORT Exception {
      v8::Local<v8::Value> exception;
      v8::Local<v8::Message> message;
    };

    // This must match the order inside `StatusValue`
    enum class Type { kSuccess, kTimeout, kErrorMessage, kException };
    using StatusValue = absl::variant<Success, Timeout, std::string, Exception>;

    Status();
    Status(const Status& other) = delete;
    Status(Status&& other);
    ~Status();

    Status& operator=(const Status&) = delete;
    Status& operator=(Status&&);

    static Status MakeTimeout(std::string timeout_message) {
      Timeout t;
      t.timeout_message = std::move(timeout_message);
      return Status(std::move(t));
    }

    static Status MakeException(v8::Local<v8::Value> exception,
                                v8::Local<v8::Message> message) {
      Exception e;
      e.exception = exception;
      e.message = message;
      return Status(std::move(e));
    }

    static Status MakeErrorMessage(std::string message) {
      return Status(StatusValue(std::move(message)));
    }

    static Status MakeSuccess() {
      return Status(StatusValue(Success::kSuccessTag));
    }

    Type type() const { return static_cast<Type>(value_.index()); }

    bool is_success() const { return type() == Type::kSuccess; }
    bool is_timeout() const { return type() == Type::kTimeout; }

    // Serializes the conversion error message. This can only be called if
    // the status is not a success.
    std::string ConvertToErrorString(v8::Isolate* isolate) const;

    // Propagates any error to v8, raising exceptions if needed. This is
    // intended to be called from method bindings to propagate the failures in
    // type-checking their arguments up to the caller.
    void PropagateErrorsToV8(AuctionV8Helper* v8_helper);

    const Exception& GetException() const {
      DCHECK_EQ(type(), Type::kException);
      return absl::get<Exception>(value_);
    }

   private:
    explicit Status(StatusValue value);

    StatusValue value_;
  };

  // For values that should be converted to WebIDL "unrestricted double" type.
  // Unlike "double" it permits NaN and +/- infinity.
  static Status Convert(v8::Isolate* isolate,
                        std::string_view error_prefix,
                        std::initializer_list<std::string_view> error_subject,
                        v8::Local<v8::Value> value,
                        UnrestrictedDouble& out);

  // For values that should be converted to WebIDL "double" type.
  static Status Convert(v8::Isolate* isolate,
                        std::string_view error_prefix,
                        std::initializer_list<std::string_view> error_subject,
                        v8::Local<v8::Value> value,
                        double& out);

  // For values that should be converted to WebIDL "boolean" type.
  static Status Convert(v8::Isolate* isolate,
                        std::string_view error_prefix,
                        std::initializer_list<std::string_view> error_subject,
                        v8::Local<v8::Value> value,
                        bool& out);

  // For values that should be converted to WebIDL "DOMString" type.
  static Status Convert(v8::Isolate* isolate,
                        std::string_view error_prefix,
                        std::initializer_list<std::string_view> error_subject,
                        v8::Local<v8::Value> value,
                        std::string& out);

  // For values that should be converted to WebIDL "DOMString" type, with the
  // destination using 16-bit strings.
  static Status Convert(v8::Isolate* isolate,
                        std::string_view error_prefix,
                        std::initializer_list<std::string_view> error_subject,
                        v8::Local<v8::Value> value,
                        std::u16string& out);

  // For values that should be converted to WebIDL "bigint" type.
  static Status Convert(v8::Isolate* isolate,
                        std::string_view error_prefix,
                        std::initializer_list<std::string_view> error_subject,
                        v8::Local<v8::Value> value,
                        v8::Local<v8::BigInt>& out);

  // For values that should be converted to WebIDL "long" type.
  static Status Convert(v8::Isolate* isolate,
                        std::string_view error_prefix,
                        std::initializer_list<std::string_view> error_subject,
                        v8::Local<v8::Value> value,
                        int32_t& out);

  // For values that should be converted to WebIDL "unsigned long" type.
  static Status Convert(v8::Isolate* isolate,
                        std::string_view error_prefix,
                        std::initializer_list<std::string_view> error_subject,
                        v8::Local<v8::Value> value,
                        uint32_t& out);

  // For values that should be converted to WebIDL "(bigint or long)" type.
  static Status Convert(v8::Isolate* isolate,
                        std::string_view error_prefix,
                        std::initializer_list<std::string_view> error_subject,
                        v8::Local<v8::Value> value,
                        absl::variant<int32_t, v8::Local<v8::BigInt>>& out);

  // For values that should be converted to WebIDL "any" type.
  // This just passes the incoming value through, and is here for benefit of
  // DictConverter; it should not be used directly.
  static Status Convert(v8::Isolate* isolate,
                        std::string_view error_prefix,
                        std::initializer_list<std::string_view> error_subject,
                        v8::Local<v8::Value> value,
                        v8::Local<v8::Value>& out);

  // Tries to use `iterator_factory` returned by CheckForSequence to iterate
  // over `iterable`. Entries will be provided one-by-one to `item_callback`
  // (0 to kSequenceLengthLimit times).
  //
  // `item_callback` is expected to typecheck its input and return the status,
  // with failures interrupting the conversion and propagated up.
  static Status ConvertSequence(
      AuctionV8Helper* v8_helper,
      std::string_view error_prefix,
      std::initializer_list<std::string_view> error_subject,
      v8::Local<v8::Object> iterable,
      v8::Local<v8::Object> iterator_factory,
      base::RepeatingCallback<Status(v8::Local<v8::Value>)> item_callback);

  // Check if given `maybe_iterable` can be treated as a sequence.
  // If it can be, sets `result` to the value of the @@iterator property and
  // returns success.
  //
  // If trying to look up the @@iterator property throws an exception or times
  // out, or returns in a non-null-or-undefined value that's not a callable
  // object, returns a failure.
  //
  // Otherwise, returns success and keeps `result` unchanged. This denotes
  // `maybe_iterable` not being a sequence, and that it can be used as some
  // other type in a union containing a sequence.
  static Status CheckForSequence(
      v8::Isolate* isolate,
      std::string_view error_prefix,
      std::initializer_list<std::string_view> error_subject,
      v8::Local<v8::Object> maybe_iterable,
      v8::Local<v8::Object>& result);

  // Makes a failure result based on state of `catcher`. If nothing is set on
  // `catcher`, will report a conversion failure.
  static Status MakeConversionFailure(
      const v8::TryCatch& catcher,
      std::string_view error_prefix,
      std::initializer_list<std::string_view> error_subject,
      std::string_view type_name);
};

// Tries to convert to WebIDL Record<DOMString, USVString>.
// `time_limit_scope` must have a non-null time limit.
CONTENT_EXPORT IdlConvert::Status ConvertRecord(
    AuctionV8Helper* v8_helper,
    AuctionV8Helper::TimeLimitScope& time_limit_scope,
    std::string_view error_prefix,
    std::initializer_list<std::string_view> error_subject,
    v8::Local<v8::Value> value,
    std::vector<std::pair<std::string, std::string>>& out);

// DictConverter helps convert a v8::Value that's supposed to be a WebIDL
// dictionary to C++ values one field at a time, following the appropriate
// semantics. Please see the constructor comment for more details.
//
// After construction, you should call GetOptional/GetRequired/
// GetOptionalSequence as appropriate for all the fields in the dictionary in
// lexicographic order. Unlike gin, this will do type conversions.
//
// All the Get... methods return true on success, false on failure.
// In particular, if GetOptional() is called on a field that's missing,
// the out-param is set to nullopt, and true is returned.
//
// In case of failure all further Get... ops will fail, and the state of
// out-param is unpredictable. You can use ErrorMessage() to get the
// description of the first detected error.
//
// The output type is what determines the conversion --- see the various
// `IdlConvert::Convert` overloads, except sequences are handled specially, via
//  GetOptionalSequence.
class CONTENT_EXPORT DictConverter {
 public:
  // Prepares to convert `value` to a WebIDL dictionary.
  //
  // Since fields conversions may loop infinitely, a `time_limit_scope` must
  // exist during its operation, and one with non-null time limit.
  //
  // Expects `v8_helper` and `time_limit_scope` will outlive `this`.
  // `error_prefix` will be prepended to error messages produced by
  // DictConverter itself (but not for those from exceptions thrown by user
  // code invoked during conversion, since that already has line information).
  DictConverter(AuctionV8Helper* v8_helper,
                AuctionV8Helper::TimeLimitScope& time_limit_scope,
                std::string error_prefix,
                v8::Local<v8::Value> value);
  ~DictConverter();

  template <typename T>
  bool GetRequired(std::string_view field, T& out) {
    if (is_failed()) {
      return false;
    }

    v8::Local<v8::Value> val = GetMember(field);
    if (is_failed()) {
      return false;
    }

    if (val->IsUndefined()) {
      MarkFailed(base::StrCat({"Required field '", field, "' is undefined."}));
      return false;
    }

    status_ = IdlConvert::Convert(v8_helper_->isolate(), error_prefix_,
                                  {"field '", field, "'"}, val, out);
    return is_success();
  }

  template <typename T>
  bool GetOptional(std::string_view field, std::optional<T>& out) {
    if (is_failed()) {
      return false;
    }

    v8::Local<v8::Value> val = GetMember(field);
    if (is_failed()) {
      return false;
    }

    if (val->IsUndefined()) {
      out = std::nullopt;
      return true;
    }

    out.emplace();
    status_ = IdlConvert::Convert(v8_helper_->isolate(), error_prefix_,
                                  {"field '", field, "'"}, val, out.value());
    return is_success();
  }

  // Gets an optional sequence field `field`. If the field exists,
  // `exists_callback` will be called, and then entries will be provided
  // one-by-one to `item_callback` (0 to kSequenceLengthLimit times).
  // `item_callback` is expected to typecheck its input and return status as
  // appropriate; with failures forwarded to `this`.
  bool GetOptionalSequence(
      std::string_view field,
      base::OnceClosure exists_callback,
      base::RepeatingCallback<IdlConvert::Status(v8::Local<v8::Value>)>
          item_callback);

  std::string ErrorMessage() const;

  // Returns conversion status. This clears any remembered errors.
  IdlConvert::Status TakeStatus() { return std::move(status_); }

  // Overrides status information with `status`.
  // `this` should not be in a failed state. The intent is to forward conversion
  // success/failure from recursive conversions.
  void SetStatus(IdlConvert::Status status);

  bool is_failed() const { return !is_success(); }

  bool is_success() const { return status_.is_success(); }

  // This is non-empty only when an exception specifically got thrown by
  // something invoked during conversion; not for any errors synthesized here.
  v8::MaybeLocal<v8::Value> FailureException() const;

  // Returns true if conversion failed because execution timeout has been
  // reached.
  bool FailureIsTimeout() const;

 private:
  // This can mark failure.
  v8::Local<v8::Value> GetMember(std::string_view field);

  void MarkFailed(std::string_view fail_message);
  void MarkFailedWithTimeout(std::string_view fail_message);
  void MarkFailedWithException(const v8::TryCatch& catcher);

  raw_ptr<AuctionV8Helper> v8_helper_;
  std::string error_prefix_;
  v8::Local<v8::Object> object_;  // Can be empty if undefined/null.

  // This gets latched to a non-success on first error, and all further ops fail
  // fast after that. This is needed because user-provided custom type coercions
  // can have side effects, so we should not run them when the process already
  // failed.
  IdlConvert::Status status_;
};

// ArgsConverter helps convert argument lists, one argument at a time.
class CONTENT_EXPORT ArgsConverter {
 public:
  // This checks that there are at least `min_required_args` arguments.
  //
  // `v8_helper` and `args` are expected to outlast `this`, and be non-null.
  ArgsConverter(AuctionV8Helper* v8_helper,
                AuctionV8Helper::TimeLimitScope& time_limit_scope,
                std::string error_prefix,
                const v8::FunctionCallbackInfo<v8::Value>* args,
                int min_required_args);
  ~ArgsConverter();

  template <typename T>
  bool ConvertArg(int pos, std::string_view arg_name, T& out) {
    if (is_failed()) {
      return false;
    }
    status_ =
        IdlConvert::Convert(v8_helper_->isolate(), error_prefix_,
                            {"argument '", arg_name, "'"}, (*args_)[pos], out);
    return is_success();
  }

  IdlConvert::Status TakeStatus() { return std::move(status_); }

  // Overrides status information with `status`.
  // `this` should not be in a failed state. The intent is to forward conversion
  // success/failure from recursive conversions.
  void SetStatus(IdlConvert::Status status);

  bool is_failed() const { return !is_success(); }

  bool is_success() const { return status_.is_success(); }

 private:
  raw_ptr<AuctionV8Helper> v8_helper_;
  std::string error_prefix_;
  const raw_ref<const v8::FunctionCallbackInfo<v8::Value>> args_;
  IdlConvert::Status status_;
};

}  // namespace auction_worklet

#endif  // CONTENT_SERVICES_AUCTION_WORKLET_WEBIDL_COMPAT_H_