summaryrefslogtreecommitdiff
path: root/deps/v8/test/fuzzer/wasm-streaming.cc
blob: 23a282a5534ea48cf869e2d4d73c13c42256a535 (plain)
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
// Copyright 2022 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 <memory>
#include <string>

#include "include/v8-isolate.h"
#include "src/api/api-inl.h"
#include "src/flags/flags.h"
#include "src/wasm/streaming-decoder.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-objects-inl.h"
#include "test/fuzzer/fuzzer-support.h"
#include "test/fuzzer/wasm-fuzzer-common.h"

namespace v8::internal::wasm::fuzzer {

// Some properties of the compilation result to check. Extend if needed.
struct CompilationResult {
  bool failed = false;
  std::string error_message;

  // If successful:
  uint32_t imported_functions = 0;
  uint32_t declared_functions = 0;

  static CompilationResult ForFailure(std::string error_message) {
    DCHECK(!error_message.empty());
    return {true, std::move(error_message)};
  }

  static CompilationResult ForSuccess(const WasmModule* module) {
    return {false,
            {},
            module->num_imported_functions,
            module->num_declared_functions};
  }
};

class TestResolver : public CompilationResultResolver {
 public:
  explicit TestResolver(Isolate* isolate) : isolate_(isolate) {}

  void OnCompilationSucceeded(Handle<WasmModuleObject> module) override {
    done_ = true;
    native_module_ = module->shared_native_module();
  }

  void OnCompilationFailed(Handle<Object> error_reason) override {
    done_ = true;
    failed_ = true;
    Handle<String> str =
        Object::ToString(isolate_, error_reason).ToHandleChecked();
    error_message_.assign(str->ToCString().get());
  }

  bool done() const { return done_; }

  bool failed() const { return failed_; }

  const std::shared_ptr<NativeModule>& native_module() const {
    return native_module_;
  }

  const std::string& error_message() const { return error_message_; }

 private:
  Isolate* isolate_;
  bool done_ = false;
  bool failed_ = false;
  std::string error_message_;
  std::shared_ptr<NativeModule> native_module_;
};

CompilationResult CompileStreaming(v8_fuzzer::FuzzerSupport* support,
                                   WasmFeatures enabled_features,
                                   base::Vector<const uint8_t> data,
                                   uint8_t config) {
  v8::Isolate* isolate = support->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  CompilationResult result;
  std::weak_ptr<NativeModule> weak_native_module;
  {
    HandleScope handle_scope{i_isolate};
    auto resolver = std::make_shared<TestResolver>(i_isolate);
    Handle<Context> context = v8::Utils::OpenHandle(*support->GetContext());
    std::shared_ptr<StreamingDecoder> stream =
        GetWasmEngine()->StartStreamingCompilation(
            i_isolate, enabled_features, context, "wasm-streaming-fuzzer",
            resolver);

    if (data.size() > 0) {
      size_t split = config % data.size();
      stream->OnBytesReceived(data.SubVector(0, split));
      stream->OnBytesReceived(data.SubVectorFrom(split));
    }
    stream->Finish();

    // Wait for the promise to resolve or reject.
    while (!resolver->done()) {
      support->PumpMessageLoop(platform::MessageLoopBehavior::kWaitForWork);
      isolate->PerformMicrotaskCheckpoint();
    }

    if (resolver->failed()) {
      return CompilationResult::ForFailure(resolver->error_message());
    }

    result = CompilationResult::ForSuccess(resolver->native_module()->module());
    weak_native_module = resolver->native_module();
  }
  // Collect garbage until the native module is collected. This ensures that we
  // recompile the module for sync compilation instead of taking it from the
  // cache.
  // If this turns out to be too slow, we could try to explicitly clear the
  // cache, but we have to be careful not to break other internal assumptions
  // then (because we have several identical modules / scripts).
  while (weak_native_module.lock()) {
    isolate->RequestGarbageCollectionForTesting(
        v8::Isolate::kFullGarbageCollection);
  }
  return result;
}

CompilationResult CompileSync(Isolate* isolate, WasmFeatures enabled_features,
                              base::Vector<const uint8_t> data) {
  ErrorThrower thrower{isolate, "wasm-streaming-fuzzer"};
  Handle<WasmModuleObject> module_object;
  CompilationResult result;
  if (!GetWasmEngine()
           ->SyncCompile(isolate, enabled_features, &thrower,
                         ModuleWireBytes{data})
           .ToHandle(&module_object)) {
    auto result = CompilationResult::ForFailure(thrower.error_msg());
    thrower.Reset();
    return result;
  }
  return CompilationResult::ForSuccess(module_object->module());
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
  if (size < 1) return 0;

  v8_fuzzer::FuzzerSupport* support = v8_fuzzer::FuzzerSupport::Get();
  v8::Isolate* isolate = support->GetIsolate();
  Isolate* i_isolate = reinterpret_cast<Isolate*>(isolate);

  v8::Isolate::Scope isolate_scope(isolate);
  v8::HandleScope handle_scope(isolate);
  v8::Context::Scope context_scope(support->GetContext());

  // We explicitly enable staged/experimental WebAssembly features here to
  // increase fuzzer coverage. For libfuzzer fuzzers it is not possible that the
  // fuzzer enables the flag by itself.
  EnableExperimentalWasmFeatures(isolate);

  // Limit the maximum module size to avoid OOM.
  v8_flags.wasm_max_module_size = 256 * KB;

  WasmFeatures enabled_features = WasmFeatures::FromIsolate(i_isolate);

  base::Vector<const uint8_t> data_vec{data, size - 1};
  uint8_t config = data[size - 1];

  CompilationResult streaming_result =
      CompileStreaming(support, enabled_features, data_vec, config);

  CompilationResult sync_result =
      CompileSync(i_isolate, enabled_features, data_vec);

  if (streaming_result.failed != sync_result.failed) {
    const char* error_msg = streaming_result.failed
                                ? streaming_result.error_message.c_str()
                                : sync_result.error_message.c_str();
    FATAL(
        "Streaming compilation did%s fail, sync compilation did%s. "
        "Error message: %s\n",
        streaming_result.failed ? "" : " not", sync_result.failed ? "" : " not",
        error_msg);
  }
  // TODO(12922): Enable this test later, after other bugs are flushed out.
  // if (strcmp(streaming_result.error_message.begin(),
  //            sync_result.error_message.begin()) != 0) {
  //   FATAL("Error messages differ: %s / %s\n",
  //         streaming_result.error_message.begin(),
  //         sync_result.error_message.begin());
  // }
  CHECK_EQ(streaming_result.imported_functions, sync_result.imported_functions);
  CHECK_EQ(streaming_result.declared_functions, sync_result.declared_functions);

  // We should not leave pending exceptions behind.
  DCHECK(!i_isolate->has_pending_exception());

  return 0;
}

}  // namespace v8::internal::wasm::fuzzer