skia2/fuzz/FuzzDDLThreading.cpp
John Stiles 3ab1292dad Fix null dereference in ~PromiseImageInfo.
While working on an unrelated fuzzing task, I noticed in the logs that
~PromiseImageInfo was crashing on a null dereference while the fuzzers
were doing smoke checks. It looks like fuzzer was being detected as
"broken" and presumably is not being run: http://screen/AKPKmyx7mHJ5nir

This CL should resolve the null dereference, but I don't know if this
indicates a deeper problem.

Bug: skia:12851
Change-Id: I4a44891783504372f28ab4b320c01a14df0892a7
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/497436
Reviewed-by: Robert Phillips <robertphillips@google.com>
Commit-Queue: John Stiles <johnstiles@google.com>
Auto-Submit: John Stiles <johnstiles@google.com>
2022-01-21 20:18:04 +00:00

280 lines
11 KiB
C++

/*
* Copyright 2021 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "fuzz/Fuzz.h"
#include "fuzz/FuzzCommon.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkDeferredDisplayList.h"
#include "include/core/SkDeferredDisplayListRecorder.h"
#include "include/core/SkExecutor.h"
#include "include/core/SkPromiseImageTexture.h"
#include "include/core/SkSize.h"
#include "include/core/SkSurface.h"
#include "include/gpu/GrDirectContext.h"
#include "include/private/SkDeque.h"
#include "include/private/SkMutex.h"
#include "include/private/SkNoncopyable.h"
#include "include/private/SkTemplates.h"
#include "include/private/SkThreadID.h"
#include "src/core/SkTaskGroup.h"
#include "src/image/SkImage_Gpu.h"
#include "tools/gpu/GrContextFactory.h"
#include <atomic>
#include <memory>
#include <queue>
using ContextType = sk_gpu_test::GrContextFactory::ContextType;
// be careful: `foo(make_fuzz_t<T>(f), make_fuzz_t<U>(f))` is undefined.
// In fact, all make_fuzz_foo() functions have this potential problem.
// Use sequence points!
template <typename T>
inline T make_fuzz_t(Fuzz* fuzz) {
T t;
fuzz->next(&t);
return t;
}
class DDLFuzzer;
// This class stores the state of a given promise image owned by the fuzzer. It acts as the
// context for the callback procs of the promise image.
class PromiseImageInfo : public SkNVRefCnt<PromiseImageInfo>, SkNoncopyable {
public:
enum class State : int {
kInitial,
kTriedToFulfill,
kDone
};
~PromiseImageInfo() {
// If we hit this, then the image or the texture will outlive this object which is bad.
SkASSERT_RELEASE(!fImage || fImage->unique());
SkASSERT_RELEASE(!fTexture || fTexture->unique());
fImage.reset();
fTexture.reset();
State s = fState;
SkASSERT_RELEASE(s == State::kDone);
}
DDLFuzzer* fFuzzer = nullptr;
sk_sp<SkImage> fImage;
// At the moment, the atomicity of this isn't used because all our promise image callbacks
// happen on the same thread. See the TODO below about them unreffing them off the GPU thread.
std::atomic<State> fState{State::kInitial};
sk_sp<SkPromiseImageTexture> fTexture;
};
static constexpr int kPromiseImageCount = 8;
static constexpr SkISize kPromiseImageSize{16, 16};
static constexpr int kPromiseImagesPerDDL = 4;
static constexpr int kRecordingThreadCount = 4;
static constexpr int kIterationCount = 10000;
// A one-shot runner object for fuzzing our DDL threading. It creates an array of promise images,
// and concurrently records DDLs that reference them, playing each DDL back on the GPU thread.
// The backing textures for promise images may be recycled into a pool, or not, for each case
// as determined by the fuzzing data.
class DDLFuzzer : SkNoncopyable {
public:
DDLFuzzer(Fuzz*, ContextType);
void run();
sk_sp<SkPromiseImageTexture> fulfillPromiseImage(PromiseImageInfo&);
void releasePromiseImage(PromiseImageInfo&);
private:
void initPromiseImage(int index);
void recordAndPlayDDL();
bool isOnGPUThread() const { return SkGetThreadID() == fGpuThread; }
bool isOnMainThread() const { return SkGetThreadID() == fMainThread; }
Fuzz* fFuzz = nullptr;
GrDirectContext* fContext = nullptr;
SkAutoTArray<PromiseImageInfo> fPromiseImages{kPromiseImageCount};
sk_sp<SkSurface> fSurface;
SkSurfaceCharacterization fSurfaceCharacterization;
std::unique_ptr<SkExecutor> fGpuExecutor = SkExecutor::MakeFIFOThreadPool(1, false);
std::unique_ptr<SkExecutor> fRecordingExecutor =
SkExecutor::MakeFIFOThreadPool(kRecordingThreadCount, false);
SkTaskGroup fGpuTaskGroup{*fGpuExecutor};
SkTaskGroup fRecordingTaskGroup{*fRecordingExecutor};
SkThreadID fGpuThread = kIllegalThreadID;
SkThreadID fMainThread = SkGetThreadID();
std::queue<sk_sp<SkPromiseImageTexture>> fReusableTextures;
sk_gpu_test::GrContextFactory fContextFactory;
};
DDLFuzzer::DDLFuzzer(Fuzz* fuzz, ContextType contextType) : fFuzz(fuzz) {
sk_gpu_test::ContextInfo ctxInfo = fContextFactory.getContextInfo(contextType);
sk_gpu_test::TestContext* testCtx = ctxInfo.testContext();
fContext = ctxInfo.directContext();
if (!fContext) {
return;
}
SkISize canvasSize = kPromiseImageSize;
canvasSize.fWidth *= kPromiseImagesPerDDL;
SkImageInfo ii = SkImageInfo::Make(canvasSize, kRGBA_8888_SkColorType, kPremul_SkAlphaType);
fSurface = SkSurface::MakeRenderTarget(fContext, SkBudgeted::kNo, ii);
if (!fSurface || !fSurface->characterize(&fSurfaceCharacterization)) {
return;
}
testCtx->makeNotCurrent();
fGpuTaskGroup.add([&]{
testCtx->makeCurrent();
fGpuThread = SkGetThreadID();
});
fGpuTaskGroup.wait();
for (int i = 0; i < kPromiseImageCount; ++i) {
this->initPromiseImage(i);
}
}
sk_sp<SkPromiseImageTexture> DDLFuzzer::fulfillPromiseImage(PromiseImageInfo& promiseImage) {
using State = PromiseImageInfo::State;
if (!this->isOnGPUThread()) {
fFuzz->signalBug();
}
bool success = make_fuzz_t<bool>(fFuzz);
State prior = promiseImage.fState.exchange(State::kTriedToFulfill, std::memory_order_relaxed);
if (prior != State::kInitial || promiseImage.fTexture != nullptr) {
fFuzz->signalBug();
}
if (!success) {
return nullptr;
}
// Try reusing an existing texture if we can and if the fuzzer wills it.
if (!fReusableTextures.empty() && make_fuzz_t<bool>(fFuzz)) {
promiseImage.fTexture = std::move(fReusableTextures.front());
fReusableTextures.pop();
return promiseImage.fTexture;
}
bool finishedBECreate = false;
auto markFinished = [](void* context) {
*(bool*)context = true;
};
GrBackendTexture backendTex = fContext->createBackendTexture(kPromiseImageSize.width(),
kPromiseImageSize.height(),
kRGBA_8888_SkColorType,
SkColors::kRed,
GrMipMapped::kNo,
GrRenderable::kYes,
GrProtected::kNo,
markFinished,
&finishedBECreate);
SkASSERT_RELEASE(backendTex.isValid());
while (!finishedBECreate) {
fContext->checkAsyncWorkCompletion();
}
promiseImage.fTexture = SkPromiseImageTexture::Make(backendTex);
return promiseImage.fTexture;
}
void DDLFuzzer::releasePromiseImage(PromiseImageInfo& promiseImage) {
using State = PromiseImageInfo::State;
// TODO: This requirement will go away when we unref promise images off the GPU thread.
if (!this->isOnGPUThread()) {
fFuzz->signalBug();
}
State old = promiseImage.fState.exchange(State::kInitial, std::memory_order_relaxed);
if (old != State::kTriedToFulfill) {
fFuzz->signalBug();
}
// If we failed to fulfill, then nothing to be done.
if (!promiseImage.fTexture) {
return;
}
bool reuse = make_fuzz_t<bool>(fFuzz);
if (reuse) {
fReusableTextures.push(std::move(promiseImage.fTexture));
} else {
fContext->deleteBackendTexture(promiseImage.fTexture->backendTexture());
}
promiseImage.fTexture = nullptr;
}
static sk_sp<SkPromiseImageTexture> fuzz_promise_image_fulfill(void* ctxIn) {
PromiseImageInfo& fuzzPromiseImage = *(PromiseImageInfo*)ctxIn;
return fuzzPromiseImage.fFuzzer->fulfillPromiseImage(fuzzPromiseImage);
}
static void fuzz_promise_image_release(void* ctxIn) {
PromiseImageInfo& fuzzPromiseImage = *(PromiseImageInfo*)ctxIn;
fuzzPromiseImage.fFuzzer->releasePromiseImage(fuzzPromiseImage);
}
void DDLFuzzer::initPromiseImage(int index) {
PromiseImageInfo& promiseImage = fPromiseImages[index];
promiseImage.fFuzzer = this;
GrBackendFormat backendFmt = fContext->defaultBackendFormat(kRGBA_8888_SkColorType,
GrRenderable::kYes);
promiseImage.fImage = SkImage::MakePromiseTexture(fContext->threadSafeProxy(),
backendFmt,
kPromiseImageSize,
GrMipMapped::kNo,
kTopLeft_GrSurfaceOrigin,
kRGBA_8888_SkColorType,
kUnpremul_SkAlphaType,
SkColorSpace::MakeSRGB(),
&fuzz_promise_image_fulfill,
&fuzz_promise_image_release,
&promiseImage);
}
void DDLFuzzer::recordAndPlayDDL() {
SkASSERT(!this->isOnGPUThread() && !this->isOnMainThread());
SkDeferredDisplayListRecorder recorder(fSurfaceCharacterization);
SkCanvas* canvas = recorder.getCanvas();
// Draw promise images in a strip
for (int i = 0; i < kPromiseImagesPerDDL; i++) {
int xOffset = i * kPromiseImageSize.width();
int j;
// Pick random promise images to draw.
fFuzz->nextRange(&j, 0, kPromiseImageCount - 1);
canvas->drawImage(fPromiseImages[j].fImage, xOffset, 0);
}
sk_sp<SkDeferredDisplayList> ddl = recorder.detach();
fGpuTaskGroup.add([=, ddl{std::move(ddl)}]{
bool success = fSurface->draw(std::move(ddl));
if (!success) {
fFuzz->signalBug();
}
});
}
void DDLFuzzer::run() {
if (!fSurface) {
return;
}
fRecordingTaskGroup.batch(kIterationCount, [=](int i) {
this->recordAndPlayDDL();
});
fRecordingTaskGroup.wait();
fGpuTaskGroup.add([=] {
while (!fReusableTextures.empty()) {
sk_sp<SkPromiseImageTexture> gpuTexture = std::move(fReusableTextures.front());
fContext->deleteBackendTexture(gpuTexture->backendTexture());
fReusableTextures.pop();
}
fContextFactory.destroyContexts();
// TODO: Release promise images not on the GPU thread.
fPromiseImages.reset(0);
});
fGpuTaskGroup.wait();
}
DEF_FUZZ(DDLThreadingGL, fuzz) {
DDLFuzzer(fuzz, ContextType::kGL_ContextType).run();
}