Implement AST fuzzer

This change implements a new fuzzer. It mutates a WGSL shader by traversing
the AST of a program and applying various transformations that might or might not
be semantics preserving.

Change-Id: I6b144bd1067444c3f0b815ba1a646aaf6e739b52
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/52160
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Vasyl Teliman <vasniktel@gmail.com>
Reviewed-by: Alastair Donaldson <allydonaldson@googlemail.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
This commit is contained in:
Vasyl Teliman 2021-07-13 12:01:25 +00:00 committed by Tint LUCI CQ
parent 54d1ee6f11
commit c6bcab02fd
31 changed files with 2702 additions and 10 deletions

View File

@ -51,6 +51,7 @@ option(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" ON)
option(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON)
option(TINT_BUILD_FUZZERS "Build fuzzers" OFF)
option(TINT_BUILD_SPIRV_TOOLS_FUZZER "Build SPIRV-Tools fuzzer" OFF)
option(TINT_BUILD_AST_FUZZER "Build AST fuzzer" OFF)
option(TINT_BUILD_TESTS "Build tests" ${TINT_BUILD_TESTS_DEFAULT})
option(TINT_BUILD_AS_OTHER_OS "Override OS detection to force building of *_other.cc files" OFF)
option(TINT_BUILD_REMOTE_COMPILE "Build the remote-compile tool for validating shaders on a remote machine" OFF)
@ -73,6 +74,7 @@ message(STATUS "Tint build SPIR-V writer: ${TINT_BUILD_SPV_WRITER}")
message(STATUS "Tint build WGSL writer: ${TINT_BUILD_WGSL_WRITER}")
message(STATUS "Tint build fuzzers: ${TINT_BUILD_FUZZERS}")
message(STATUS "Tint build SPIRV-Tools fuzzer: ${TINT_BUILD_SPIRV_TOOLS_FUZZER}")
message(STATUS "Tint build AST fuzzer: ${TINT_BUILD_AST_FUZZER}")
message(STATUS "Tint build tests: ${TINT_BUILD_TESTS}")
message(STATUS "Tint build with ASAN: ${TINT_ENABLE_ASAN}")
message(STATUS "Tint build with MSAN: ${TINT_ENABLE_MSAN}")
@ -85,13 +87,13 @@ find_package(PythonInterp 3 REQUIRED)
if (${TINT_BUILD_SPIRV_TOOLS_FUZZER})
message(STATUS "TINT_BUILD_SPIRV_TOOLS_FUZZER is ON - setting
TINT_BUILD_FUZZERS,
TINT_BUILD_SPV_READER,
TINT_BUILD_WGSL_READER,
TINT_BUILD_WGSL_WRITER,
TINT_BUILD_HLSL_WRITER,
TINT_BUILD_MSL_WRITER,
TINT_BUILD_SPV_WRITER to ON")
TINT_BUILD_FUZZERS
TINT_BUILD_SPV_READER
TINT_BUILD_WGSL_READER
TINT_BUILD_WGSL_WRITER
TINT_BUILD_HLSL_WRITER
TINT_BUILD_MSL_WRITER
TINT_BUILD_SPV_WRITER to ON")
set(TINT_BUILD_FUZZERS ON)
set(TINT_BUILD_SPV_READER ON)
set(TINT_BUILD_WGSL_READER ON)
@ -101,6 +103,22 @@ if (${TINT_BUILD_SPIRV_TOOLS_FUZZER})
set(TINT_BUILD_SPV_WRITER ON)
endif()
if (${TINT_BUILD_AST_FUZZER})
message(STATUS "TINT_BUILD_AST_FUZZER is ON - setting
TINT_BUILD_FUZZERS
TINT_BUILD_WGSL_READER
TINT_BUILD_WGSL_WRITER
TINT_BUILD_SPV_WRITER
TINT_BUILD_MSL_WRITER
TINT_BUILD_HLSL_WRITER to ON")
set(TINT_BUILD_FUZZERS ON)
set(TINT_BUILD_WGSL_READER ON)
set(TINT_BUILD_WGSL_WRITER ON)
set(TINT_BUILD_SPV_WRITER ON)
set(TINT_BUILD_MSL_WRITER ON)
set(TINT_BUILD_HLSL_WRITER ON)
endif()
set(TINT_ROOT_SOURCE_DIR ${PROJECT_SOURCE_DIR})
# CMake < 3.15 sets /W3 in CMAKE_CXX_FLAGS. Remove it if it's there.

2
DEPS
View File

@ -45,7 +45,7 @@ deps = {
'/google/googletest.git@' + Var('googletest_revision'),
'third_party/protobuf': Var('chromium_git') + Var('github') +
'/protocolbuffers/protobuf.git@' + Var('protobuf_revision'),
'/protocolbuffers/protobuf.git@' + Var('protobuf_revision'),
}
hooks = [

View File

@ -789,6 +789,8 @@ INPUT = CODE_OF_CONDUCT.md \
fuzzers/tint_spirv_tools_fuzzer \
src \
tools/src \
fuzzers/tint_spirv_tools_fuzzer \
fuzzers/tint_ast_fuzzer
# This tag can be used to specify the character encoding of the source files
# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses

View File

@ -77,3 +77,7 @@ endif()
if (${TINT_BUILD_SPIRV_TOOLS_FUZZER})
add_subdirectory(tint_spirv_tools_fuzzer)
endif()
if (${TINT_BUILD_AST_FUZZER})
add_subdirectory(tint_ast_fuzzer)
endif()

View File

@ -0,0 +1,102 @@
# Copyright 2021 The Tint Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
if (NOT (${CMAKE_CXX_COMPILER_ID} MATCHES Clang))
message(FATAL_ERROR "Libfuzzer is supported only on clang")
endif ()
set(PROTOBUF_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/protobufs/tint_ast_fuzzer.proto)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/protobufs)
add_custom_command(
OUTPUT
${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.cc
${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.h
COMMAND
"protobuf::protoc" -I=${CMAKE_CURRENT_SOURCE_DIR}/protobufs
--cpp_out=${CMAKE_CURRENT_BINARY_DIR}/protobufs ${PROTOBUF_SOURCES}
DEPENDS ${PROTOBUF_SOURCES}
COMMENT "Generate protobuf sources from proto definition file.")
set(LIBTINT_AST_FUZZER_SOURCES
mt_rng.h
mutation.h
mutation_finder.h
mutation_finders/replace_identifiers.h
mutations/replace_identifier.h
mutator.h
node_id_map.h
probability_context.h
protobufs/tint_ast_fuzzer.h
random_number_generator.h
util.h
${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.h)
set(LIBTINT_AST_FUZZER_SOURCES ${LIBTINT_AST_FUZZER_SOURCES}
mt_rng.cc
mutation.cc
mutation_finder.cc
mutation_finders/replace_identifiers.cc
mutations/replace_identifier.cc
mutator.cc
node_id_map.cc
probability_context.cc
random_number_generator.cc
${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.cc)
set_source_files_properties(${CMAKE_CURRENT_BINARY_DIR}/protobufs/tint_ast_fuzzer.pb.cc PROPERTIES COMPILE_FLAGS -w)
# Add static library target.
add_library(libtint_ast_fuzzer STATIC ${LIBTINT_AST_FUZZER_SOURCES})
target_link_libraries(libtint_ast_fuzzer protobuf::libprotobuf libtint)
tint_default_compile_options(libtint_ast_fuzzer)
target_include_directories(libtint_ast_fuzzer PRIVATE ${CMAKE_BINARY_DIR})
set(AST_FUZZER_SOURCES
cli.cc
cli.h
fuzzer.cc
../tint_common_fuzzer.cc
../tint_common_fuzzer.h)
set_source_files_properties(fuzzer.cc PROPERTIES COMPILE_FLAGS -Wno-missing-prototypes)
# Add libfuzzer target.
add_executable(tint_ast_fuzzer ${AST_FUZZER_SOURCES})
target_link_libraries(tint_ast_fuzzer libtint-fuzz libtint_ast_fuzzer)
tint_default_compile_options(tint_ast_fuzzer)
target_compile_definitions(tint_ast_fuzzer PRIVATE CUSTOM_MUTATOR)
target_include_directories(tint_ast_fuzzer PRIVATE ${CMAKE_BINARY_DIR})
# Add tests.
if (${TINT_BUILD_TESTS})
set(TEST_SOURCES
mutations/replace_identifier_test.cc)
add_executable(tint_ast_fuzzer_unittests ${TEST_SOURCES})
target_include_directories(
tint_ast_fuzzer_unittests PRIVATE ${gmock_SOURCE_DIR}/include)
target_link_libraries(tint_ast_fuzzer_unittests gmock_main libtint_ast_fuzzer)
tint_default_compile_options(tint_ast_fuzzer_unittests)
target_compile_options(tint_ast_fuzzer_unittests PRIVATE
-Wno-global-constructors
-Wno-weak-vtables
-Wno-covered-switch-default)
target_include_directories(tint_ast_fuzzer_unittests PRIVATE ${CMAKE_BINARY_DIR})
add_test(NAME tint_ast_fuzzer_unittests COMMAND tint_ast_fuzzer_unittests)
endif ()

View File

@ -0,0 +1,162 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "fuzzers/tint_ast_fuzzer/cli.h"
#include <cstring>
#include <iostream>
#include <limits>
#include <sstream>
#include <string>
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
namespace {
const char* const kHelpMessage = R"(
This is a fuzzer for the Tint compiler that works by mutating the AST.
Below is a list of all supported parameters for this fuzzer. You may want to
run it with -help=1 to check out libfuzzer parameters.
--enable_all_mutations=
If `false`, the fuzzer will only apply mutations from a
randomly selected subset of mutation types. Otherwise,
all mutation types will be considered. This must be one
of `true` or `false` (without `). By default it's `false`.
--fuzzing_target=
Specifies the shading language to target during fuzzing.
This must be one or a combination of `wgsl`, `spv`, `hlsl`,
`msl` (without `) separated by commas. By default it's
`wgsl,msl,hlsl,spv`.
--help
Show this message. Note that there is also a -help=1
parameter that will display libfuzzer's help message.
--mutation_batch_size=
The number of mutations to apply in a single libfuzzer
mutation session. This must be a numeric value that fits
in type `uint32_t`. By default it's 5.
--record_mutations=
Whether to record applied mutations in the protobuf
message. This is useful to debug the fuzzer and during
metamorphic fuzzing. The value must be one of `true` or
`false` (without `). By default it's `false`.
)";
bool HasPrefix(const char* str, const char* prefix) {
return strncmp(str, prefix, strlen(prefix)) == 0;
}
[[noreturn]] void InvalidParam(const char* param) {
std::cout << "Invalid value for " << param << std::endl;
std::cout << kHelpMessage << std::endl;
exit(1);
}
bool ParseBool(const char* value, bool* out) {
if (!strcmp(value, "true")) {
*out = true;
} else if (!strcmp(value, "false")) {
*out = false;
} else {
return false;
}
return true;
}
bool ParseUint32(const char* value, uint32_t* out) {
auto parsed = strtoul(value, nullptr, 10);
if (parsed > std::numeric_limits<uint32_t>::max()) {
return false;
}
*out = static_cast<uint32_t>(parsed);
return true;
}
bool ParseFuzzingTarget(const char* value, FuzzingTarget* out) {
if (!strcmp(value, "wgsl")) {
*out = FuzzingTarget::kWgsl;
} else if (!strcmp(value, "spv")) {
*out = FuzzingTarget::kSpv;
} else if (!strcmp(value, "msl")) {
*out = FuzzingTarget::kMsl;
} else if (!strcmp(value, "hlsl")) {
*out = FuzzingTarget::kHlsl;
} else {
return false;
}
return true;
}
} // namespace
CliParams ParseCliParams(int argc, const char* const* argv) {
CliParams cli_params;
auto help = false;
for (int i = 1; i < argc; ++i) {
auto param = argv[i];
if (HasPrefix(param, "--record_mutations=")) {
if (!ParseBool(param + sizeof("--record_mutations=") - 1,
&cli_params.record_mutations)) {
InvalidParam(param);
}
} else if (HasPrefix(param, "--enable_all_mutations=")) {
if (!ParseBool(param + sizeof("--enable_all_mutations=") - 1,
&cli_params.enable_all_mutations)) {
InvalidParam(param);
}
} else if (HasPrefix(param, "--mutation_batch_size=")) {
if (!ParseUint32(param + sizeof("--mutation_batch_size=") - 1,
&cli_params.mutation_batch_size)) {
InvalidParam(param);
}
} else if (HasPrefix(param, "--fuzzing_target=")) {
auto result = FuzzingTarget::kNone;
std::stringstream ss(param + sizeof("--fuzzing_target=") - 1);
for (std::string value; std::getline(ss, value, ',');) {
auto tmp = FuzzingTarget::kNone;
if (!ParseFuzzingTarget(value.c_str(), &tmp)) {
InvalidParam(param);
}
result = result | tmp;
}
if (result == FuzzingTarget::kNone) {
InvalidParam(param);
}
cli_params.fuzzing_target = result;
} else if (!strcmp(param, "--help")) {
help = true;
}
}
if (help) {
std::cout << kHelpMessage << std::endl;
exit(0);
}
return cli_params;
}
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,74 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef FUZZERS_TINT_AST_FUZZER_CLI_H_
#define FUZZERS_TINT_AST_FUZZER_CLI_H_
#include <cstdint>
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
/// The backend this fuzzer will test.
enum class FuzzingTarget {
kNone = 0,
kHlsl = 1 << 0,
kMsl = 1 << 1,
kSpv = 1 << 2,
kWgsl = 1 << 3,
kAll = kHlsl | kMsl | kSpv | kWgsl
};
inline FuzzingTarget operator|(FuzzingTarget a, FuzzingTarget b) {
return static_cast<FuzzingTarget>(static_cast<int>(a) | static_cast<int>(b));
}
inline FuzzingTarget operator&(FuzzingTarget a, FuzzingTarget b) {
return static_cast<FuzzingTarget>(static_cast<int>(a) & static_cast<int>(b));
}
/// CLI parameters accepted by the fuzzer. Type --help in the CLI to see the
/// help message
struct CliParams {
/// Whether to record applied mutations.
bool record_mutations = false;
/// Whether to use all mutation finders or only a randomly selected subset of
/// them.
bool enable_all_mutations = false;
/// The maximum number of mutations applied during a single mutation session
/// (i.e. a call to `ast_fuzzer::Mutate` function).
uint32_t mutation_batch_size = 5;
/// Compiler backends we want to fuzz.
FuzzingTarget fuzzing_target = FuzzingTarget::kAll;
};
/// @brief Parses CLI parameters.
///
/// This function will exit the process with non-zero return code if some
/// parameters are invalid.
///
/// @param argc - the total number of parameters.
/// @param argv - array of all CLI parameters.
/// @return parsed parameters.
CliParams ParseCliParams(int argc, const char* const* argv);
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_AST_FUZZER_CLI_H_

View File

@ -0,0 +1,154 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <cstddef>
#include <cstdint>
#include "fuzzers/tint_ast_fuzzer/cli.h"
#include "fuzzers/tint_ast_fuzzer/mt_rng.h"
#include "fuzzers/tint_ast_fuzzer/mutator.h"
#include "fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h"
#include "fuzzers/tint_common_fuzzer.h"
#include "src/reader/wgsl/parser.h"
#include "src/writer/wgsl/generator.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
namespace {
CliParams cli_params{};
extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
// Parse CLI parameters. `ParseCliParams` will call `exit` if some parameter
// is invalid.
cli_params = ParseCliParams(*argc, *argv);
return 0;
}
extern "C" size_t LLVMFuzzerCustomMutator(uint8_t* data,
size_t size,
size_t max_size,
unsigned seed) {
protobufs::MutatorState mutator_state;
auto success = mutator_state.ParseFromArray(data, static_cast<int>(size));
(void)success; // This variable will be unused in release mode.
assert(success && "Can't parse protobuf message");
tint::Source::File file("test.wgsl", mutator_state.program());
auto program = reader::wgsl::Parse(&file);
protobufs::MutationSequence* mutation_sequence = nullptr;
if (cli_params.record_mutations) {
// If mutations are being recorded, then `mutator_state.program` is the
// original (unmodified) program and it is necessary to replay all
// mutations.
mutation_sequence = mutator_state.mutable_mutation_sequence();
program = Replay(std::move(program), *mutation_sequence);
if (!program.IsValid()) {
std::cout << "Replayer produced invalid WGSL:" << std::endl
<< " seed: " << seed << std::endl
<< program.Diagnostics().str() << std::endl;
return 0;
}
}
// Run the mutator.
MtRng mt_rng(seed);
ProbabilityContext probability_context(&mt_rng);
program = Mutate(std::move(program), &probability_context,
cli_params.enable_all_mutations,
cli_params.mutation_batch_size, mutation_sequence);
if (!program.IsValid()) {
std::cout << "Mutator produced invalid WGSL:" << std::endl
<< " seed: " << seed << std::endl
<< program.Diagnostics().str() << std::endl;
return 0;
}
if (!cli_params.record_mutations) {
// If mutations are not being recorded, then the mutated `program` must be
// stored into the `mutator_state`.
writer::wgsl::Generator generator(&program);
if (!generator.Generate()) {
std::cout << "Can't generate WGSL for valid tint::Program:" << std::endl
<< " seed: " << seed << std::endl
<< generator.error() << std::endl;
return 0;
}
*mutator_state.mutable_program() = generator.result();
}
if (mutator_state.ByteSizeLong() > max_size) {
return 0;
}
success = mutator_state.SerializeToArray(data, static_cast<int>(max_size));
assert(success && "Can't serialize a protobuf message");
return mutator_state.ByteSizeLong();
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
if (size == 0) {
return 0;
}
protobufs::MutatorState mutator_state;
auto success = mutator_state.ParseFromArray(data, static_cast<int>(size));
(void)success; // This variable will be unused in release mode.
assert(success && "Can't parse a protobuf message");
std::string program_text;
if (cli_params.record_mutations) {
// If mutations are being recorded, then it's necessary to replay them
// before invoking the system under test.
Source::File file("test.wgsl", mutator_state.program());
auto program =
Replay(reader::wgsl::Parse(&file), mutator_state.mutation_sequence());
assert(program.IsValid() && "Replayed program is invalid");
writer::wgsl::Generator generator(&program);
success = generator.Generate();
assert(success && "Can't generate a shader for the valid tint::Program");
program_text = generator.result();
} else {
program_text.assign(data, data + size);
}
std::pair<FuzzingTarget, OutputFormat> targets[] = {
{FuzzingTarget::kWgsl, OutputFormat::kWGSL},
{FuzzingTarget::kHlsl, OutputFormat::kHLSL},
{FuzzingTarget::kMsl, OutputFormat::kMSL},
{FuzzingTarget::kSpv, OutputFormat::kSpv}};
for (auto target : targets) {
if ((target.first & cli_params.fuzzing_target) != target.first) {
continue;
}
CommonFuzzer fuzzer(InputFormat::kWGSL, target.second);
fuzzer.EnableInspector();
fuzzer.Run(reinterpret_cast<const uint8_t*>(program_text.data()),
program_text.size());
}
return 0;
}
} // namespace
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,44 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "fuzzers/tint_ast_fuzzer/mt_rng.h"
#include <cassert>
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
namespace {
template <typename T>
T RandomUInt(std::mt19937* rng, T bound) {
assert(bound > 0 && "`bound` must be positive");
return std::uniform_int_distribution<T>(0, bound - 1)(*rng);
}
} // namespace
MtRng::MtRng(uint32_t seed) : rng_(seed) {}
uint32_t MtRng::RandomUint32(uint32_t bound) {
return RandomUInt(&rng_, bound);
}
uint64_t MtRng::RandomUint64(uint64_t bound) {
return RandomUInt(&rng_, bound);
}
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,45 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef FUZZERS_TINT_AST_FUZZER_MT_RNG_H_
#define FUZZERS_TINT_AST_FUZZER_MT_RNG_H_
#include <random>
#include "fuzzers/tint_ast_fuzzer/random_number_generator.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
/// The random number generator that uses STL's Mersenne Twister (std::mt19937)
/// under the hood.
class MtRng : public RandomNumberGenerator {
public:
/// @brief Initializes this RNG with some `seed`.
/// @param seed - passed down to the `std::mt19937`.
explicit MtRng(uint32_t seed);
uint32_t RandomUint32(uint32_t bound) override;
uint64_t RandomUint64(uint64_t bound) override;
private:
std::mt19937 rng_;
};
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_AST_FUZZER_MT_RNG_H_

View File

@ -0,0 +1,40 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "fuzzers/tint_ast_fuzzer/mutation.h"
#include <cassert>
#include "fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
Mutation::~Mutation() = default;
std::unique_ptr<Mutation> Mutation::FromMessage(
const protobufs::Mutation& message) {
switch (message.mutation_case()) {
case protobufs::Mutation::kReplaceIdentifier:
return std::make_unique<MutationReplaceIdentifier>(
message.replace_identifier());
case protobufs::Mutation::MUTATION_NOT_SET:
assert(false && "Mutation is not set");
}
}
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,86 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef FUZZERS_TINT_AST_FUZZER_MUTATION_H_
#define FUZZERS_TINT_AST_FUZZER_MUTATION_H_
#include <memory>
#include <vector>
#include "fuzzers/tint_ast_fuzzer/node_id_map.h"
#include "fuzzers/tint_ast_fuzzer/protobufs/tint_ast_fuzzer.h"
#include "src/clone_context.h"
#include "src/program.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
/// The base class for all the mutations in the fuzzer. Children must override
/// three methods:
/// - `IsApplicable` - checks whether it is possible to apply the mutation
/// in a manner that will lead to a valid program.
/// - `Apply` - applies the mutation.
/// - `ToMessage` - converts the mutation data into a protobuf message.
class Mutation {
public:
/// Virtual destructor.
virtual ~Mutation();
/// @brief Determines whether this mutation is applicable to the `program`.
///
/// @param program - the program this mutation will be applied to. The program
/// must be valid.
/// @param node_id_map - the map from `tint::ast::` nodes to their ids.
/// @return `true` if `Apply` method can be called without breaking the
/// semantics of the `program`.
/// @return `false` otherwise.
virtual bool IsApplicable(const tint::Program& program,
const NodeIdMap& node_id_map) const = 0;
/// @brief Applies this mutation to the `clone_context`.
///
/// Precondition: `IsApplicable` must return `true` when invoked on the same
/// `node_id_map` and `clone_context->src` instance of `tint::Program`. A new
/// `tint::Program` that arises in `clone_context` must be valid.
///
/// @param node_id_map - the map from `tint::ast::` nodes to their ids.
/// @param clone_context - the context that will clone the program with some
/// changes introduced by this mutation.
/// @param new_node_id_map - this map will store ids for the mutated and
/// cloned program. This argument cannot be a `nullptr` nor can it point
/// to the same object as `node_id_map`.
virtual void Apply(const NodeIdMap& node_id_map,
tint::CloneContext* clone_context,
NodeIdMap* new_node_id_map) const = 0;
/// @return a protobuf message for this mutation.
virtual protobufs::Mutation ToMessage() const = 0;
/// @brief Converts a protobuf message into the mutation instance.
///
/// @param message - a protobuf message.
/// @return the instance of this class.
static std::unique_ptr<Mutation> FromMessage(
const protobufs::Mutation& message);
};
using MutationList = std::vector<std::unique_ptr<Mutation>>;
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_AST_FUZZER_MUTATION_H_

View File

@ -0,0 +1,25 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "fuzzers/tint_ast_fuzzer/mutation_finder.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
MutationFinder::~MutationFinder() = default;
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,77 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_
#define FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_
#include <memory>
#include <vector>
#include "fuzzers/tint_ast_fuzzer/mutation.h"
#include "fuzzers/tint_ast_fuzzer/node_id_map.h"
#include "fuzzers/tint_ast_fuzzer/probability_context.h"
#include "src/program.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
/// Instances of this class traverse the `tint::Program`, looking for
/// opportunities to apply mutations and return them to the caller.
///
/// Ideally, the behaviour of this class (precisely, its `FindMutations` method)
/// should not be probabilistic. This is useful when mutation finders are used
/// for test case reduction, because it enables the test case reducer to
/// systematically explore all available mutations. There may be some
/// exceptions, however. For example, if a huge number of mutations is returned,
/// it would make sense to apply only a probabilistically selected subset of
/// them.
class MutationFinder {
public:
/// Virtual destructor.
virtual ~MutationFinder();
/// @brief Traverses the `program`, looking for opportunities to apply
/// mutations.
///
/// @param program - the program being fuzzed.
/// @param node_id_map - a map from `tint::ast::` nodes in the `program` to
/// their unique ids.
/// @param probability_context - determines various probabilistic stuff in the
/// mutator. This should ideally be used as less as possible.
/// @return all the found mutations.
virtual MutationList FindMutations(
const tint::Program& program,
const NodeIdMap& node_id_map,
ProbabilityContext* probability_context) const = 0;
/// @brief Compute a probability of applying a single mutation, returned by
/// this class.
///
/// @param probability_context - contains information about various
/// non-deterministic stuff in the fuzzer.
/// @return a number in the range [0; 100] which is a chance of applying a
/// mutation.
virtual uint32_t GetChanceOfApplyingMutation(
ProbabilityContext* probability_context) const = 0;
};
using MutationFinderList = std::vector<std::unique_ptr<MutationFinder>>;
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_AST_FUZZER_MUTATION_FINDER_H_

View File

@ -0,0 +1,79 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "fuzzers/tint_ast_fuzzer/mutation_finders/replace_identifiers.h"
#include <memory>
#include "fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
#include "fuzzers/tint_ast_fuzzer/util.h"
#include "src/sem/expression.h"
#include "src/sem/statement.h"
#include "src/sem/variable.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
MutationList MutationFinderReplaceIdentifiers::FindMutations(
const tint::Program& program,
const NodeIdMap& node_id_map,
ProbabilityContext* probability_context) const {
MutationList result;
// Go through each variable in the AST and for each user of that variable, try
// to replace it with some other variable usage.
for (const auto* node : program.SemNodes().Objects()) {
const auto* sem_variable = tint::As<sem::Variable>(node);
if (!sem_variable) {
continue;
}
// Iterate over all users of `sem_variable`.
for (const auto* user : sem_variable->Users()) {
// Get all variables that can be used to replace the `user` of
// `sem_variable`.
auto candidate_variables = util::GetAllVarsInScope(
program, user->Stmt(), [user](const sem::Variable* var) {
return var != user->Variable() && var->Type() == user->Type();
});
if (candidate_variables.empty()) {
// No suitable replacements have been found.
continue;
}
const auto* replacement =
candidate_variables[probability_context->GetRandomIndex(
candidate_variables)];
result.push_back(std::make_unique<MutationReplaceIdentifier>(
node_id_map.GetId(user->Declaration()),
node_id_map.GetId(replacement->Declaration())));
}
}
return result;
}
uint32_t MutationFinderReplaceIdentifiers::GetChanceOfApplyingMutation(
ProbabilityContext* probability_context) const {
return probability_context->GetChanceOfReplacingIdentifiers();
}
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,42 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_
#define FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_
#include "fuzzers/tint_ast_fuzzer/mutation_finder.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
/// Looks for opportunities to apply `MutationReplaceIdentifier`.
///
/// Concretely, for each variable in the module, tries to replace its users with
/// the uses of some other variables.
class MutationFinderReplaceIdentifiers : public MutationFinder {
public:
MutationList FindMutations(
const tint::Program& program,
const NodeIdMap& node_id_map,
ProbabilityContext* probability_context) const override;
uint32_t GetChanceOfApplyingMutation(
ProbabilityContext* probability_context) const override;
};
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_AST_FUZZER_MUTATION_FINDERS_REPLACE_IDENTIFIERS_H_

View File

@ -0,0 +1,106 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
#include <utility>
#include "fuzzers/tint_ast_fuzzer/util.h"
#include "src/program_builder.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
MutationReplaceIdentifier::MutationReplaceIdentifier(
protobufs::MutationReplaceIdentifier message)
: message_(std::move(message)) {}
MutationReplaceIdentifier::MutationReplaceIdentifier(uint32_t use_id,
uint32_t replacement_id) {
message_.set_use_id(use_id);
message_.set_replacement_id(replacement_id);
}
bool MutationReplaceIdentifier::IsApplicable(
const tint::Program& program,
const NodeIdMap& node_id_map) const {
const auto* use_ast_node = tint::As<ast::IdentifierExpression>(
node_id_map.GetNode(message_.use_id()));
if (!use_ast_node) {
// Either the `use_id` is invalid or the node is not an
// `IdentifierExpression`.
return false;
}
const auto* use_sem_node =
tint::As<sem::VariableUser>(program.Sem().Get(use_ast_node));
if (!use_sem_node) {
// Either the semantic information is not present for a `use_node` or that
// node is not a variable user.
return false;
}
const auto* replacement_ast_node =
tint::As<ast::Variable>(node_id_map.GetNode(message_.replacement_id()));
if (!replacement_ast_node) {
// Either the `replacement_id` is invalid or is not an id of a variable.
return false;
}
const auto* replacement_sem_node = program.Sem().Get(replacement_ast_node);
if (!replacement_sem_node) {
return false;
}
if (replacement_sem_node == use_sem_node->Variable()) {
return false;
}
auto in_scope =
util::GetAllVarsInScope(program, use_sem_node->Stmt(),
[replacement_sem_node](const sem::Variable* var) {
return var == replacement_sem_node;
});
if (in_scope.empty()) {
// The replacement variable is not in scope.
return false;
}
return use_sem_node->Type() == replacement_sem_node->Type();
}
void MutationReplaceIdentifier::Apply(const NodeIdMap& node_id_map,
tint::CloneContext* clone_context,
NodeIdMap* new_node_id_map) const {
const auto* use_node = node_id_map.GetNode(message_.use_id());
const auto* replacement_var =
tint::As<ast::Variable>(node_id_map.GetNode(message_.replacement_id()));
auto* cloned_replacement =
clone_context->dst->Expr(clone_context->Clone(use_node->source()),
clone_context->Clone(replacement_var->symbol()));
clone_context->Replace(use_node, cloned_replacement);
new_node_id_map->Add(cloned_replacement, message_.use_id());
}
protobufs::Mutation MutationReplaceIdentifier::ToMessage() const {
protobufs::Mutation mutation;
*mutation.mutable_replace_identifier() = message_;
return mutation;
}
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint

View File

@ -0,0 +1,77 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_
#define FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_
#include "fuzzers/tint_ast_fuzzer/mutation.h"
#include "src/sem/variable.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
/// @see MutationReplaceIdentifier::Apply
class MutationReplaceIdentifier : public Mutation {
public:
/// @brief Constructs an instance of this mutation from a protobuf message.
/// @param message - protobuf message
explicit MutationReplaceIdentifier(
protobufs::MutationReplaceIdentifier message);
/// @brief Constructor.
/// @param use_id - the id of a variable user.
/// @param replacement_id - the id of a variable to replace the `use_id`.
MutationReplaceIdentifier(uint32_t use_id, uint32_t replacement_id);
/// @copybrief Mutation::IsApplicable
///
/// The mutation is applicable iff:
/// - `use_id` is a valid id of an `ast::IdentifierExpression`, that
/// references a variable.
/// - `replacement_id` is a valid id of an `ast::Variable`.
/// - The identifier expression doesn't reference the variable of a
/// `replacement_id`.
/// - The variable with `replacement_id` is in scope of an identifier
/// expression with `use_id`.
/// - The identifier expression and the variable have the same type.
///
/// @copydetails Mutation::IsApplicable
bool IsApplicable(const tint::Program& program,
const NodeIdMap& node_id_map) const override;
/// @copybrief Mutation::Apply
///
/// Replaces the use of an identifier expression with `use_id` with a newly
/// created identifier expression, that references a variable with
/// `replacement_id`. The newly created identifier expression will have the
/// same id as the old one (i.e. `use_id`).
///
/// @copydetails Mutation::Apply
void Apply(const NodeIdMap& node_id_map,
tint::CloneContext* clone_context,
NodeIdMap* new_node_id_map) const override;
protobufs::Mutation ToMessage() const override;
private:
protobufs::MutationReplaceIdentifier message_;
};
} // namespace ast_fuzzer
} // namespace fuzzers
} // namespace tint
#endif // FUZZERS_TINT_AST_FUZZER_MUTATIONS_REPLACE_IDENTIFIER_H_

View File

@ -0,0 +1,723 @@
// Copyright 2021 The Tint Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <string>
#include "gtest/gtest.h"
#include "fuzzers/tint_ast_fuzzer/mt_rng.h"
#include "fuzzers/tint_ast_fuzzer/mutations/replace_identifier.h"
#include "fuzzers/tint_ast_fuzzer/mutator.h"
#include "fuzzers/tint_ast_fuzzer/probability_context.h"
#include "fuzzers/tint_ast_fuzzer/node_id_map.h"
#include "src/ast/call_statement.h"
#include "src/program_builder.h"
#include "src/reader/wgsl/parser.h"
#include "src/writer/wgsl/generator.h"
namespace tint {
namespace fuzzers {
namespace ast_fuzzer {
namespace {
TEST(ReplaceIdentifierTest, NotApplicable_Simple) {
std::string content = R"(
fn main() {
let a = 5;
let c = 6;
let b = a + 5;
let d = vec2<i32>(1, 2);
let e = d.x;
}
)";
Source::File file("test.wgsl", content);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
const auto& main_fn_stmts =
program.AST().Functions()[0]->body()->statements();
const auto* a_var =
main_fn_stmts[0]->As<ast::VariableDeclStatement>()->variable();
ASSERT_NE(a_var, nullptr);
const auto* b_var =
main_fn_stmts[2]->As<ast::VariableDeclStatement>()->variable();
ASSERT_NE(b_var, nullptr);
const auto* e_var =
main_fn_stmts[4]->As<ast::VariableDeclStatement>()->variable();
ASSERT_NE(e_var, nullptr);
auto a_var_id = node_id_map.GetId(a_var);
ASSERT_NE(a_var_id, 0);
auto b_var_id = node_id_map.GetId(b_var);
ASSERT_NE(b_var_id, 0);
const auto* sum_expr = b_var->constructor()->As<ast::BinaryExpression>();
ASSERT_NE(sum_expr, nullptr);
auto a_ident_id = node_id_map.GetId(sum_expr->lhs());
ASSERT_NE(a_ident_id, 0);
auto sum_expr_id = node_id_map.GetId(sum_expr);
ASSERT_NE(sum_expr_id, 0);
auto e_var_id = node_id_map.GetId(e_var);
ASSERT_NE(e_var_id, 0);
auto vec_member_access_id = node_id_map.GetId(
e_var->constructor()->As<ast::MemberAccessorExpression>()->member());
ASSERT_NE(vec_member_access_id, 0);
// use_id is invalid.
EXPECT_FALSE(MutationReplaceIdentifier(0, a_var_id)
.IsApplicable(program, node_id_map));
// use_id is not an identifier expression.
EXPECT_FALSE(MutationReplaceIdentifier(sum_expr_id, a_var_id)
.IsApplicable(program, node_id_map));
// use_id is an identifier but not a variable user.
EXPECT_FALSE(MutationReplaceIdentifier(vec_member_access_id, a_var_id)
.IsApplicable(program, node_id_map));
// replacement_id is invalid.
EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, 0)
.IsApplicable(program, node_id_map));
// replacement_id is not a variable.
EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, sum_expr_id)
.IsApplicable(program, node_id_map));
// Can't replace a variable with itself.
EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, a_var_id)
.IsApplicable(program, node_id_map));
// Replacement is not in scope.
EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, b_var_id)
.IsApplicable(program, node_id_map));
EXPECT_FALSE(MutationReplaceIdentifier(a_ident_id, e_var_id)
.IsApplicable(program, node_id_map));
}
TEST(ReplaceIdentifierTest, GlobalVarNotInScope) {
// Can't use the global variable if it's not in scope.
std::string shader = R"(
var<private> a: i32;
fn f() {
a = 3;
}
var<private> b: i32;
)";
Source::File file("test.wgsl", shader);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
auto use_id = node_id_map.GetId(program.AST()
.Functions()[0]
->body()
->statements()[0]
->As<ast::AssignmentStatement>()
->lhs());
ASSERT_NE(use_id, 0);
auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
ASSERT_NE(replacement_id, 0);
ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
.IsApplicable(program, node_id_map));
}
TEST(ReplaceIdentifierTest, NotApplicable1) {
// Can't replace `a` with `b` since the store type is wrong (the same storage
// class though).
std::string shader = R"(
var<private> a: i32;
var<private> b: u32;
fn f() {
*&a = 4;
}
)";
Source::File file("test.wgsl", shader);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
auto replacement_id = node_id_map.GetId(program.AST().GlobalVariables()[1]);
ASSERT_NE(replacement_id, 0);
auto use_id = node_id_map.GetId(program.AST()
.Functions()[0]
->body()
->statements()[0]
->As<ast::AssignmentStatement>()
->lhs()
->As<ast::UnaryOpExpression>()
->expr()
->As<ast::UnaryOpExpression>()
->expr());
ASSERT_NE(use_id, 0);
ASSERT_FALSE(MutationReplaceIdentifier(use_id, replacement_id)
.IsApplicable(program, node_id_map));
}
TEST(ReplaceIdentifierTest, NotApplicable2) {
// Can't replace `a` with `b` since the store type is wrong (the storage
// class is different though).
std::string shader = R"(
var<private> a: i32;
fn f() {
var b: u32;
*&a = 4;
}
)";
Source::File file("test.wgsl", shader);
auto program = reader::wgsl::Parse(&file);
ASSERT_TRUE(program.IsValid()) << program.Diagnostics().str();
NodeIdMap node_id_map(program);
auto replacement_id = node_id_map.GetId(program.AST()
.Functions()[0]
->body()
->statements()[0]
->As<ast::VariableDeclStatement>()