diff --git a/src/tools/fuzzing/fuzzing.cpp b/src/tools/fuzzing/fuzzing.cpp index 2e1e9281b07..63453d5e2ac 100644 --- a/src/tools/fuzzing/fuzzing.cpp +++ b/src/tools/fuzzing/fuzzing.cpp @@ -2487,32 +2487,55 @@ void TranslateToFuzzReader::mutateJSBoundary() { if (new_ == Type::unreachable) { new_ = Type(old.getHeapType().getBottom(), NonNullable); } + assert(Type::isSubType(new_, old)); // Find all heap types between the old and new, starting from new. auto oldHeapType = old.getHeapType(); + auto oldExactness = old.getExactness(); auto newHeapType = new_.getHeapType(); - assert(HeapType::isSubType(newHeapType, oldHeapType)); - std::vector options; + auto newExactness = new_.getExactness(); + std::vector> options; while (1) { - options.push_back(newHeapType); + options.push_back({newHeapType, newExactness}); // We cannot look at a bottom type's supers (there can be many, and the // getSuperType() API doesn't return them), but can use // interestingHeapSubTypes: any subtype of old is valid. if (newHeapType.isBottom()) { - for (auto type : interestingHeapSubTypes[oldHeapType]) { - options.push_back(type); + // We can only do this when the old exactness is inexact: if old was + // (exact $A) then the only valid subtypes are (exact $A) itself, and + // the bottom type. + if (oldExactness == Inexact) { + for (auto type : interestingHeapSubTypes[oldHeapType]) { + options.push_back({type, Inexact}); + options.push_back({type, Exact}); + } + options.push_back({oldHeapType, Inexact}); + } + // Regardless of the old exactness, it is valid to add the old type as + // exact (unless the old type was a basic type). + if (!oldHeapType.isBasic()) { + options.push_back({oldHeapType, Exact}); } break; } - // Continue until we reach the old type. - if (newHeapType == oldHeapType) { + // Continue until we reach the old type and exactness. + if (newHeapType == oldHeapType && newExactness == oldExactness) { break; } + if (newExactness == Exact) { + // We are not at the old type and exactness yet (or we would have just + // stopped). Remove exactness, as the only exact result that is valid is + // newHeapType itself. That is, if the actual output is (exact $B) then + // we cannot return (exact $A) for some supertype $A, as that would + // break subtyping. + newExactness = Inexact; + continue; + } auto next = newHeapType.getSuperType(); assert(next); newHeapType = *next; } - newHeapType = pick(options); + auto [heapType, exactness] = pick(options); // Pick the nullability. auto oldNullability = old.getNullability(); @@ -2521,23 +2544,9 @@ void TranslateToFuzzReader::mutateJSBoundary() { newNullability = getNullability(); } - // Pick the exactness. - auto oldExactness = old.getExactness(); - auto newExactness = new_.getExactness(); - // We can only be exact if we are using the new heap type: that type is - // exactly what is sent here, and no intermediate heap type would be valid. - // For example, given $A :> $B :> $C, then maybeRefine($A, exact $C) can - // return exact $C, but cannot return exact $B. - // - // Also, basic heap types cannot be exact. - if (newHeapType != new_.getHeapType() || newHeapType.isBasic()) { - newExactness = Inexact; - } else if (newExactness != oldExactness) { - // TODO: once getExactness() is fixed (see there), use that - newExactness = oneIn(2) ? Exact : Inexact; - } - - return Type(newHeapType, newNullability, newExactness); + auto refined = Type(heapType, newNullability, exactness); + assert(Type::isSubType(refined, old)); + return refined; }; // Given a set of types (all params or all results), and an index among them,