skia2/modules/canvaskit/particles.js
Kevin Lubick 69e46da716 [canvaskit] Fix infrequent crash in SkFontMgr.FromData
The bug here is very subtle, as is the mitigation.

Quick background on WASM memory, there is an object
called wasmMemory (which might be hoisted into scope for
CanvasKit's pre-js functions), of type WebAssembly.Memory
which is a resizable ArrayBuffer. Emscripten provides the
JS code to initialize this and handle size increases.
Emscripten also provides TypedArray "views" into this buffer.
These are called CanvasKit.HEAPU8, CanvasKit.HEAPF32, etc.

When there is a call to CanvasKit._malloc, wasmMemory may
be resized. If that happens, the previous TypedArray views
become invalid. However, in the same call to _malloc,
emscripten will refresh the views [1]. So, dealing with
CanvasKit.HEAPU8 directly (quick aside, we never expect clients
to mess with these views, only us in our glue JS code
[e.g. interface.js]), should always be safe because if they
were to be invalidated in a call to _malloc, the views would
be refreshed before _malloc continues.

The problem that existed before was when we were passing
CanvasKit.HEAP* as a parameter to a function, in which the
function would call _malloc before using the typed array
parameter:
  //... let us suppose wasmMemory is backed by ArrayBuffer D
  copy1dArray(arr, HEAPU32);
  // The HEAPU32 TypedArray (backed by ArrayBuffer D) is stored
  // to a function parameter "dest"
    function copy1dArray(arr, dest, ptr) {
    // ...
    if (!ptr) {
      ptr = CanvasKit._malloc(arr.length * dest.BYTES_PER_ELEMENT);
      // Suppose _malloc needs to resize wasmMemory and is
      // now backed by ArrayBuffer E.
      // Note: The field CanvasKit.HEAPU32 is correctly backed
      // by ArrayBuffer E, but variable dest still points to a
      // TypedArray backed by ArrayBuffer D.
    }
    // dest.set will fail with a "neutered ArrayBuffer" error
    // because ArrayBuffer D is effectively gone (replaced by E).
    dest.set(arr, ptr / dest.BYTES_PER_ELEMENT);

The fix here is to pass in the field name indicating the TypedArray
view we want to write our data into instead of using the
view itself as the parameter.

[1] e427159553/src/preamble.js (L344)


Change-Id: I46cfb98f8bdf928b61690a5ced034a5961356398
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/294516
Reviewed-by: Nathaniel Nifong <nifong@google.com>
2020-06-05 13:32:46 +00:00

77 lines
2.7 KiB
JavaScript

// Adds compile-time JS functions to augment the CanvasKit interface.
// Specifically, anything that should only be on the Particle builds of canvaskit.
// assets is a dictionary of named blobs: { key: ArrayBuffer, ... }
// The keys should be well-behaved strings - they're turned into null-terminated
// strings for the native side.
CanvasKit.MakeParticles = function(json, assets) {
if (!CanvasKit._MakeParticles) {
throw 'Not compiled with MakeParticles';
}
if (!assets) {
return CanvasKit._MakeParticles(json, 0, nullptr, nullptr, nullptr);
}
var assetNamePtrs = [];
var assetDataPtrs = [];
var assetSizes = [];
var assetKeys = Object.keys(assets || {});
for (var i = 0; i < assetKeys.length; i++) {
var key = assetKeys[i];
var buffer = assets[key];
var data = new Uint8Array(buffer);
var iptr = CanvasKit._malloc(data.byteLength);
CanvasKit.HEAPU8.set(data, iptr);
assetDataPtrs.push(iptr);
assetSizes.push(data.byteLength);
// lengthBytesUTF8 and stringToUTF8Array are defined in the emscripten
// JS. See https://kripken.github.io/emscripten-site/docs/api_reference/preamble.js.html#stringToUTF8
// Add 1 for null terminator
var strLen = lengthBytesUTF8(key) + 1;
var strPtr = CanvasKit._malloc(strLen);
stringToUTF8(key, strPtr, strLen);
assetNamePtrs.push(strPtr);
}
// Not entirely sure if it matters, but the uintptr_t are 32 bits
// we want to copy our array of uintptr_t into the right size memory.
var namesPtr = copy1dArray(assetNamePtrs, "HEAPU32");
var assetsPtr = copy1dArray(assetDataPtrs, "HEAPU32");
var assetSizesPtr = copy1dArray(assetSizes, "HEAPU32");
var particles = CanvasKit._MakeParticles(json, assetKeys.length,
namesPtr, assetsPtr, assetSizesPtr);
// The C++ code has made copies of the asset and string data, so free our copies.
CanvasKit._free(namesPtr);
CanvasKit._free(assetsPtr);
CanvasKit._free(assetSizesPtr);
return particles;
};
CanvasKit._extraInitializations = CanvasKit._extraInitializations || [];
CanvasKit._extraInitializations.push(function() {
CanvasKit.SkParticleEffect.prototype.effectUniforms = function() {
var fptr = this._effectUniformPtr();
var numFloats = this.getEffectUniformFloatCount();
if (!fptr || numFloats <= 0) {
return new Float32Array();
}
return new Float32Array(CanvasKit.HEAPU8.buffer, fptr, numFloats);
}
CanvasKit.SkParticleEffect.prototype.particleUniforms = function() {
var fptr = this._particleUniformPtr();
var numFloats = this.getParticleUniformFloatCount();
if (!fptr || numFloats <= 0) {
return new Float32Array();
}
return new Float32Array(CanvasKit.HEAPU8.buffer, fptr, numFloats);
}
});