Skip to content

Commit 2bba2eb

Browse files
authored
Add PublicTypeValidator for use in fuzzer (#7597)
When custom descriptors is not enabled, we consider it a validation error for a public type to include an exact reference because the identity of that type will change when the binary is written and that reference becomes inexact. Once the fuzzer observes functions signatures with exact references, it will have to be careful not to export those functions and make the exact reference public when custom descriptors is disabled. Add a utility to efficiently determine whether a type is valid to be made public, memoizing results to avoid traversing any type definition more than once. Use this utility in the fuzzer.
1 parent c25a2e4 commit 2bba2eb

File tree

7 files changed

+319
-5
lines changed

7 files changed

+319
-5
lines changed

src/ir/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ set(ir_SOURCES
1616
properties.cpp
1717
LocalGraph.cpp
1818
LocalStructuralDominance.cpp
19+
public-type-validator.cpp
1920
ReFinalize.cpp
2021
return-utils.cpp
2122
stack-utils.cpp

src/ir/public-type-validator.cpp

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2025 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "ir/public-type-validator.h"
18+
#include "wasm-type.h"
19+
20+
namespace wasm {
21+
22+
bool PublicTypeValidator::isValidPublicTypeImpl(HeapType type) {
23+
assert(!features.hasCustomDescriptors());
24+
assert(!type.isBasic());
25+
// If custom descriptors is not enabled and exposing this type would make an
26+
// exact reference public, then this type is not valid to make public.
27+
// Traverse the heap types reachable from this one, looking for exact
28+
// references. Cache the findings for each group along the way to minimize
29+
// future work.
30+
struct Task {
31+
RecGroup group;
32+
bool finished;
33+
};
34+
std::vector<Task> workList{Task{type.getRecGroup(), false}};
35+
std::unordered_set<RecGroup> visiting;
36+
37+
auto markVisitingInvalid = [&]() {
38+
for (auto group : visiting) {
39+
auto [_, inserted] = allowedPublicGroupCache.insert({group, false});
40+
assert(inserted);
41+
}
42+
};
43+
44+
while (!workList.empty()) {
45+
auto task = workList.back();
46+
workList.pop_back();
47+
if (task.finished) {
48+
// We finished searching this group and the groups it reaches without
49+
// finding a problem.
50+
visiting.erase(task.group);
51+
[[maybe_unused]] auto [_, inserted] =
52+
allowedPublicGroupCache.insert({task.group, true});
53+
assert(inserted);
54+
continue;
55+
}
56+
if (auto it = allowedPublicGroupCache.find(task.group);
57+
it != allowedPublicGroupCache.end()) {
58+
if (it->second) {
59+
// We already know this group is valid. Move on to the next group.
60+
continue;
61+
} else {
62+
// This group is invalid!
63+
markVisitingInvalid();
64+
return false;
65+
}
66+
}
67+
// Check whether we are already in the process of searching this group.
68+
if (!visiting.insert(task.group).second) {
69+
continue;
70+
}
71+
// We have never seen this group before. Search it. Push a completion task
72+
// first so we will know when we have finished searching.
73+
workList.push_back({task.group, true});
74+
for (auto heapType : task.group) {
75+
// Look for invalid exact references.
76+
for (auto t : heapType.getTypeChildren()) {
77+
if (t.isExact()) {
78+
markVisitingInvalid();
79+
return false;
80+
}
81+
}
82+
// Search the referenced groups as well.
83+
for (auto t : heapType.getReferencedHeapTypes()) {
84+
if (!t.isBasic()) {
85+
workList.push_back({t.getRecGroup(), false});
86+
}
87+
}
88+
}
89+
}
90+
91+
// We searched all the reachable types and never found an exact reference.
92+
return true;
93+
}
94+
95+
} // namespace wasm

src/ir/public-type-validator.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2025 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef wasm_ir_public_type_validator_h
18+
#define wasm_ir_public_type_validator_h
19+
20+
#include "wasm-features.h"
21+
#include "wasm-type.h"
22+
23+
#include <unordered_map>
24+
25+
namespace wasm {
26+
27+
// Utility for determining whether it is valid to make a given type public given
28+
// some enabled feature set. Used in the fuzzer for determining whether a
29+
// function can be exported, for instance.
30+
class PublicTypeValidator {
31+
public:
32+
explicit PublicTypeValidator(FeatureSet features) : features(features) {}
33+
34+
bool isValidPublicType(HeapType type) {
35+
if (features.hasCustomDescriptors()) {
36+
return true;
37+
}
38+
if (type.isBasic()) {
39+
return true;
40+
}
41+
return isValidPublicTypeImpl(type);
42+
}
43+
44+
bool isValidPublicType(Type type) {
45+
if (features.hasCustomDescriptors()) {
46+
return true;
47+
}
48+
if (type.isBasic()) {
49+
return true;
50+
}
51+
if (type.isTuple()) {
52+
for (auto t : type) {
53+
if (!isValidPublicType(t)) {
54+
return false;
55+
}
56+
}
57+
return true;
58+
}
59+
assert(type.isRef());
60+
if (type.isExact()) {
61+
return false;
62+
}
63+
return isValidPublicType(type.getHeapType());
64+
}
65+
66+
private:
67+
FeatureSet features;
68+
std::unordered_map<RecGroup, bool> allowedPublicGroupCache;
69+
bool isValidPublicTypeImpl(HeapType type);
70+
};
71+
72+
} // namespace wasm
73+
74+
#endif // wasm_ir_public_type_validator_h

src/tools/fuzzing.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <ir/literal-utils.h>
2929
#include <ir/manipulation.h>
3030
#include <ir/names.h>
31+
#include <ir/public-type-validator.h>
3132
#include <ir/utils.h>
3233
#include <support/file.h>
3334
#include <tools/optimization-options.h>
@@ -341,6 +342,13 @@ class TranslateToFuzzReader {
341342
Expression* makeImportSleep(Type type);
342343
Expression* makeMemoryHashLogging();
343344

345+
// We must be careful not to add exports that have invalid public types, such
346+
// as those that reach exact types when custom descriptors is disabled.
347+
PublicTypeValidator publicTypeValidator;
348+
bool isValidPublicType(Type type) {
349+
return publicTypeValidator.isValidPublicType(type);
350+
}
351+
344352
// Function operations. The main processFunctions() loop will call addFunction
345353
// as well as modFunction().
346354
void processFunctions();

src/tools/fuzzing/fuzzing.cpp

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ TranslateToFuzzReader::TranslateToFuzzReader(Module& wasm,
3030
std::vector<char>&& input,
3131
bool closedWorld)
3232
: wasm(wasm), closedWorld(closedWorld), builder(wasm),
33-
random(std::move(input), wasm.features) {
33+
random(std::move(input), wasm.features),
34+
publicTypeValidator(wasm.features) {
3435

3536
// - funcref cannot be logged because referenced functions can be inlined or
3637
// removed during optimization
@@ -1495,11 +1496,13 @@ Function* TranslateToFuzzReader::addFunction() {
14951496
// outside.
14961497
bool validExportParams =
14971498
std::all_of(paramType.begin(), paramType.end(), [&](Type t) {
1498-
return t.isDefaultable();
1499+
return t.isDefaultable() && isValidPublicType(t);
14991500
});
1500-
if (validExportParams && (numAddedFunctions == 0 || oneIn(2)) &&
1501-
!wasm.getExportOrNull(func->name) && !preserveImportsAndExports) {
1502-
wasm.addExport(new Export(func->name, ExternalKind::Function, func->name));
1501+
if (!preserveImportsAndExports && validExportParams &&
1502+
isValidPublicType(resultType) && (numAddedFunctions == 0 || oneIn(2)) &&
1503+
!wasm.getExportOrNull(func->name)) {
1504+
wasm.addExport(
1505+
Builder::makeExport(func->name, func->name, ExternalKind::Function));
15031506
}
15041507
// add some to an elem segment
15051508
while (oneIn(3) && !random.finished()) {

test/gtest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ set(unittest_SOURCES
1818
local-graph.cpp
1919
possible-contents.cpp
2020
printing.cpp
21+
public-type-validator.cpp
2122
scc.cpp
2223
stringify.cpp
2324
suffix_tree.cpp

test/gtest/public-type-validator.cpp

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright 2025 WebAssembly Community Group participants
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "ir/public-type-validator.h"
18+
#include "wasm-type.h"
19+
20+
#include "gtest/gtest.h"
21+
22+
using namespace wasm;
23+
24+
class PublicTypeValidatorTest : public ::testing::Test {
25+
protected:
26+
HeapType EmptyStruct;
27+
HeapType RecursiveStruct;
28+
HeapType InvalidStruct;
29+
HeapType IndirectlyInvalidStruct;
30+
HeapType RecursiveInvalidStruct;
31+
HeapType EmptyStructInGroupWithInvalid;
32+
33+
void SetUp() override {
34+
TypeBuilder builder(8);
35+
36+
// Empty struct
37+
HeapType empty = builder[0];
38+
builder[0] = Struct();
39+
40+
// Recursive struct
41+
HeapType recursive = builder[1];
42+
builder[1] = Struct({Field(Type(recursive, Nullable), Mutable)});
43+
44+
// Invalid struct (when custom descriptors are disabled)
45+
HeapType invalid = builder[2];
46+
builder[2] = Struct({Field(Type(empty, Nullable, Exact), Mutable)});
47+
48+
// Indirectly invalid struct
49+
builder[3] = Struct({Field(Type(invalid, Nullable), Mutable)});
50+
51+
// Mutually recursive, indirectly invalid struct
52+
builder[4] = Struct({Field(Type(builder[5], Nullable), Mutable)});
53+
builder[5] = Struct({Field(Type(builder[4], Nullable, Exact), Mutable)});
54+
builder.createRecGroup(4, 2);
55+
56+
// Empty struct in group with invalid struct
57+
builder[6] = Struct();
58+
builder[7] = Struct({Field(Type(invalid, Nullable), Mutable)});
59+
builder.createRecGroup(6, 2);
60+
61+
auto result = builder.build();
62+
auto& built = *result;
63+
64+
EmptyStruct = built[0];
65+
RecursiveStruct = built[1];
66+
InvalidStruct = built[2];
67+
IndirectlyInvalidStruct = built[3];
68+
RecursiveInvalidStruct = built[4];
69+
EmptyStructInGroupWithInvalid = built[6];
70+
}
71+
72+
void TearDown() override { wasm::destroyAllTypesForTestingPurposesOnly(); }
73+
};
74+
75+
TEST_F(PublicTypeValidatorTest, CustomDescriptorsEnabled) {
76+
PublicTypeValidator validator(FeatureSet::CustomDescriptors);
77+
78+
EXPECT_TRUE(validator.isValidPublicType(EmptyStruct));
79+
EXPECT_TRUE(validator.isValidPublicType(RecursiveStruct));
80+
EXPECT_TRUE(validator.isValidPublicType(InvalidStruct));
81+
EXPECT_TRUE(validator.isValidPublicType(IndirectlyInvalidStruct));
82+
EXPECT_TRUE(validator.isValidPublicType(RecursiveInvalidStruct));
83+
EXPECT_TRUE(validator.isValidPublicType(EmptyStructInGroupWithInvalid));
84+
}
85+
86+
TEST_F(PublicTypeValidatorTest, CustomDescriptorsDisabled) {
87+
PublicTypeValidator validator(FeatureSet::MVP);
88+
89+
EXPECT_TRUE(validator.isValidPublicType(EmptyStruct));
90+
EXPECT_TRUE(validator.isValidPublicType(RecursiveStruct));
91+
EXPECT_FALSE(validator.isValidPublicType(InvalidStruct));
92+
EXPECT_FALSE(validator.isValidPublicType(IndirectlyInvalidStruct));
93+
EXPECT_FALSE(validator.isValidPublicType(RecursiveInvalidStruct));
94+
EXPECT_FALSE(validator.isValidPublicType(EmptyStructInGroupWithInvalid));
95+
}
96+
97+
TEST_F(PublicTypeValidatorTest, CachedResult) {
98+
PublicTypeValidator validator(FeatureSet::MVP);
99+
100+
// Check the indirectly invalid type first, then serve the query for the
101+
// directly invalid type from the cache.
102+
EXPECT_FALSE(validator.isValidPublicType(IndirectlyInvalidStruct));
103+
EXPECT_FALSE(validator.isValidPublicType(InvalidStruct));
104+
105+
// We can serve repeated queries from the cache, too.
106+
EXPECT_FALSE(validator.isValidPublicType(IndirectlyInvalidStruct));
107+
}
108+
109+
TEST_F(PublicTypeValidatorTest, BasicHeapTypes) {
110+
PublicTypeValidator validator(FeatureSet::MVP);
111+
112+
EXPECT_TRUE(validator.isValidPublicType(HeapTypes::any));
113+
EXPECT_TRUE(validator.isValidPublicType(HeapTypes::eq));
114+
EXPECT_TRUE(validator.isValidPublicType(HeapTypes::ext));
115+
EXPECT_TRUE(validator.isValidPublicType(HeapTypes::func));
116+
EXPECT_TRUE(validator.isValidPublicType(HeapTypes::none));
117+
}
118+
119+
TEST_F(PublicTypeValidatorTest, Types) {
120+
PublicTypeValidator validator(FeatureSet::MVP);
121+
122+
EXPECT_TRUE(validator.isValidPublicType(Type::i32));
123+
EXPECT_TRUE(validator.isValidPublicType(Type::i64));
124+
EXPECT_TRUE(validator.isValidPublicType(Type::f32));
125+
EXPECT_TRUE(validator.isValidPublicType(Type::f64));
126+
EXPECT_TRUE(validator.isValidPublicType(Type::v128));
127+
128+
EXPECT_TRUE(validator.isValidPublicType(Type(HeapType::any, Nullable)));
129+
EXPECT_TRUE(validator.isValidPublicType(Type(EmptyStruct, Nullable)));
130+
EXPECT_FALSE(validator.isValidPublicType(Type(EmptyStruct, Nullable, Exact)));
131+
EXPECT_FALSE(validator.isValidPublicType(Type(InvalidStruct, Nullable)));
132+
}

0 commit comments

Comments
 (0)