spirv-fuzz: Add fuzzer pass to permute function parameters (#3212)
Fixes #3194.
diff --git a/.gitignore b/.gitignore
index 196c63c..b2af56e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,3 +29,6 @@
# C-Lion
/.idea/
/cmake-build-*/
+
+# VSCode
+/.vscode/*
diff --git a/source/fuzz/CMakeLists.txt b/source/fuzz/CMakeLists.txt
index 231ea75..8fd0593 100644
--- a/source/fuzz/CMakeLists.txt
+++ b/source/fuzz/CMakeLists.txt
@@ -62,6 +62,7 @@
fuzzer_pass_obfuscate_constants.h
fuzzer_pass_outline_functions.h
fuzzer_pass_permute_blocks.h
+ fuzzer_pass_permute_function_parameters.h
fuzzer_pass_split_blocks.h
fuzzer_pass_swap_commutable_operands.h
fuzzer_util.h
@@ -104,6 +105,7 @@
transformation_merge_blocks.h
transformation_move_block_down.h
transformation_outline_function.h
+ transformation_permute_function_parameters.h
transformation_replace_boolean_constant_with_constant_binary.h
transformation_replace_constant_with_uniform.h
transformation_replace_id_with_synonym.h
@@ -150,6 +152,7 @@
fuzzer_pass_obfuscate_constants.cpp
fuzzer_pass_outline_functions.cpp
fuzzer_pass_permute_blocks.cpp
+ fuzzer_pass_permute_function_parameters.cpp
fuzzer_pass_split_blocks.cpp
fuzzer_pass_swap_commutable_operands.cpp
fuzzer_util.cpp
@@ -191,6 +194,7 @@
transformation_merge_blocks.cpp
transformation_move_block_down.cpp
transformation_outline_function.cpp
+ transformation_permute_function_parameters.cpp
transformation_replace_boolean_constant_with_constant_binary.cpp
transformation_replace_constant_with_uniform.cpp
transformation_replace_id_with_synonym.cpp
diff --git a/source/fuzz/fuzzer.cpp b/source/fuzz/fuzzer.cpp
index 945bcc5..a89c361 100644
--- a/source/fuzz/fuzzer.cpp
+++ b/source/fuzz/fuzzer.cpp
@@ -45,6 +45,7 @@
#include "source/fuzz/fuzzer_pass_obfuscate_constants.h"
#include "source/fuzz/fuzzer_pass_outline_functions.h"
#include "source/fuzz/fuzzer_pass_permute_blocks.h"
+#include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
#include "source/fuzz/fuzzer_pass_split_blocks.h"
#include "source/fuzz/fuzzer_pass_swap_commutable_operands.h"
#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
@@ -244,6 +245,9 @@
MaybeAddPass<FuzzerPassPermuteBlocks>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
+ MaybeAddPass<FuzzerPassPermuteFunctionParameters>(
+ &passes, ir_context.get(), &fact_manager, &fuzzer_context,
+ transformation_sequence_out);
MaybeAddPass<FuzzerPassSplitBlocks>(&passes, ir_context.get(),
&fact_manager, &fuzzer_context,
transformation_sequence_out);
diff --git a/source/fuzz/fuzzer_context.cpp b/source/fuzz/fuzzer_context.cpp
index a374f29..dd2d7c3 100644
--- a/source/fuzz/fuzzer_context.cpp
+++ b/source/fuzz/fuzzer_context.cpp
@@ -60,6 +60,7 @@
const std::pair<uint32_t, uint32_t> kChanceOfMovingBlockDown = {20, 50};
const std::pair<uint32_t, uint32_t> kChanceOfObfuscatingConstant = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfOutliningFunction = {10, 90};
+const std::pair<uint32_t, uint32_t> kChanceOfPermutingParameters = {30, 90};
const std::pair<uint32_t, uint32_t> kChanceOfReplacingIdWithSynonym = {10, 90};
const std::pair<uint32_t, uint32_t> kChanceOfSplittingBlock = {40, 95};
@@ -146,6 +147,8 @@
ChooseBetweenMinAndMax(kChanceOfObfuscatingConstant);
chance_of_outlining_function_ =
ChooseBetweenMinAndMax(kChanceOfOutliningFunction);
+ chance_of_permuting_parameters_ =
+ ChooseBetweenMinAndMax(kChanceOfPermutingParameters);
chance_of_replacing_id_with_synonym_ =
ChooseBetweenMinAndMax(kChanceOfReplacingIdWithSynonym);
chance_of_splitting_block_ = ChooseBetweenMinAndMax(kChanceOfSplittingBlock);
diff --git a/source/fuzz/fuzzer_context.h b/source/fuzz/fuzzer_context.h
index 1d8b7bf..813f232 100644
--- a/source/fuzz/fuzzer_context.h
+++ b/source/fuzz/fuzzer_context.h
@@ -62,6 +62,40 @@
return result;
}
+ // Randomly shuffles a |sequence| between |lo| and |hi| indices inclusively.
+ // |lo| and |hi| must be valid indices to the |sequence|
+ template <typename T>
+ void Shuffle(std::vector<T>* sequence, size_t lo, size_t hi) const {
+ auto& array = *sequence;
+
+ if (array.empty()) {
+ return;
+ }
+
+ assert(lo <= hi && hi < array.size() && "lo and/or hi indices are invalid");
+
+ // i > lo to account for potential infinite loop when lo == 0
+ for (size_t i = hi; i > lo; --i) {
+ auto index =
+ random_generator_->RandomUint32(static_cast<uint32_t>(i - lo + 1));
+
+ if (lo + index != i) {
+ // Introduce std::swap to the scope but don't use it
+ // directly since there might be a better overload
+ using std::swap;
+ swap(array[lo + index], array[i]);
+ }
+ }
+ }
+
+ // Ramdomly shuffles a |sequence|
+ template <typename T>
+ void Shuffle(std::vector<T>* sequence) const {
+ if (!sequence->empty()) {
+ Shuffle(sequence, 0, sequence->size() - 1);
+ }
+ }
+
// Yields an id that is guaranteed not to be used in the module being fuzzed,
// or to have been issued before.
uint32_t GetFreshId();
@@ -139,6 +173,9 @@
uint32_t GetChanceOfOutliningFunction() {
return chance_of_outlining_function_;
}
+ uint32_t GetChanceOfPermutingParameters() {
+ return chance_of_permuting_parameters_;
+ }
uint32_t GetChanceOfReplacingIdWithSynonym() {
return chance_of_replacing_id_with_synonym_;
}
@@ -203,6 +240,7 @@
uint32_t chance_of_moving_block_down_;
uint32_t chance_of_obfuscating_constant_;
uint32_t chance_of_outlining_function_;
+ uint32_t chance_of_permuting_parameters_;
uint32_t chance_of_replacing_id_with_synonym_;
uint32_t chance_of_splitting_block_;
diff --git a/source/fuzz/fuzzer_pass.cpp b/source/fuzz/fuzzer_pass.cpp
index 73a0285..a76f10d 100644
--- a/source/fuzz/fuzzer_pass.cpp
+++ b/source/fuzz/fuzzer_pass.cpp
@@ -22,6 +22,7 @@
#include "source/fuzz/transformation_add_global_undef.h"
#include "source/fuzz/transformation_add_type_boolean.h"
#include "source/fuzz/transformation_add_type_float.h"
+#include "source/fuzz/transformation_add_type_function.h"
#include "source/fuzz/transformation_add_type_int.h"
#include "source/fuzz/transformation_add_type_matrix.h"
#include "source/fuzz/transformation_add_type_pointer.h"
@@ -179,6 +180,26 @@
return result;
}
+uint32_t FuzzerPass::FindOrCreateFunctionType(
+ uint32_t return_type_id, const std::vector<uint32_t>& argument_id) {
+ // FindFunctionType has a sigle argument for OpTypeFunction operands
+ // so we will have to copy them all in this vector
+ std::vector<uint32_t> type_ids(argument_id.size() + 1);
+ type_ids[0] = return_type_id;
+ std::copy(argument_id.begin(), argument_id.end(), type_ids.begin() + 1);
+
+ // Check if type exists
+ auto existing_id = fuzzerutil::FindFunctionType(GetIRContext(), type_ids);
+ if (existing_id) {
+ return existing_id;
+ }
+
+ auto result = GetFuzzerContext()->GetFreshId();
+ ApplyTransformation(
+ TransformationAddTypeFunction(result, return_type_id, argument_id));
+ return result;
+}
+
uint32_t FuzzerPass::FindOrCreateVectorType(uint32_t component_type_id,
uint32_t component_count) {
assert(component_count >= 2 && component_count <= 4 &&
diff --git a/source/fuzz/fuzzer_pass.h b/source/fuzz/fuzzer_pass.h
index 910eed1..46ee408 100644
--- a/source/fuzz/fuzzer_pass.h
+++ b/source/fuzz/fuzzer_pass.h
@@ -112,6 +112,12 @@
// instruction does not exist, a transformation is applied to add it.
uint32_t FindOrCreate32BitFloatType();
+ // Returns the id of an OpTypeFunction %<return_type_id> %<...argument_id>
+ // instruction. If such an instruction doesn't exist, a transformation
+ // is applied to create a new one.
+ uint32_t FindOrCreateFunctionType(uint32_t return_type_id,
+ const std::vector<uint32_t>& argument_id);
+
// Returns the id of an OpTypeVector instruction, with |component_type_id|
// (which must already exist) as its base type, and |component_count|
// elements (which must be in the range [2, 4]). If such an instruction does
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.cpp b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
new file mode 100644
index 0000000..2c49860
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.cpp
@@ -0,0 +1,81 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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 <numeric>
+#include <vector>
+
+#include "source/fuzz/fuzzer_context.h"
+#include "source/fuzz/fuzzer_pass_permute_function_parameters.h"
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/instruction_descriptor.h"
+#include "source/fuzz/transformation_permute_function_parameters.h"
+
+namespace spvtools {
+namespace fuzz {
+
+FuzzerPassPermuteFunctionParameters::FuzzerPassPermuteFunctionParameters(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations)
+ : FuzzerPass(ir_context, fact_manager, fuzzer_context, transformations) {}
+
+FuzzerPassPermuteFunctionParameters::~FuzzerPassPermuteFunctionParameters() =
+ default;
+
+void FuzzerPassPermuteFunctionParameters::Apply() {
+ for (const auto& function : *GetIRContext()->module()) {
+ uint32_t function_id = function.result_id();
+
+ // Skip the function if it is an entry point
+ if (fuzzerutil::FunctionIsEntryPoint(GetIRContext(), function_id)) {
+ continue;
+ }
+
+ if (!GetFuzzerContext()->ChoosePercentage(
+ GetFuzzerContext()->GetChanceOfPermutingParameters())) {
+ continue;
+ }
+
+ // Compute permutation for parameters
+ auto* function_type =
+ fuzzerutil::GetFunctionType(GetIRContext(), &function);
+ assert(function_type && "Function type is null");
+
+ // Don't take return type into account
+ uint32_t arg_size = function_type->NumInOperands() - 1;
+
+ // Create a vector, fill it with [0, n-1] values and shuffle it
+ std::vector<uint32_t> permutation(arg_size);
+ std::iota(permutation.begin(), permutation.end(), 0);
+ GetFuzzerContext()->Shuffle(&permutation);
+
+ // Create a new OpFunctionType instruction with permuted arguments
+ // if needed
+ auto result_type_id = function_type->GetSingleWordInOperand(0);
+ std::vector<uint32_t> argument_ids;
+
+ for (auto index : permutation) {
+ // +1 to take function's return type into account
+ argument_ids.push_back(function_type->GetSingleWordInOperand(index + 1));
+ }
+
+ // Apply our transformation
+ ApplyTransformation(TransformationPermuteFunctionParameters(
+ function_id, FindOrCreateFunctionType(result_type_id, argument_ids),
+ permutation));
+ }
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/fuzzer_pass_permute_function_parameters.h b/source/fuzz/fuzzer_pass_permute_function_parameters.h
new file mode 100644
index 0000000..bc79804
--- /dev/null
+++ b/source/fuzz/fuzzer_pass_permute_function_parameters.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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 SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_PARAMETERS_H_
+#define SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_PARAMETERS_H_
+
+#include "source/fuzz/fuzzer_pass.h"
+
+namespace spvtools {
+namespace fuzz {
+
+// Fuzzer pass that, given a non-entry-point function taking n parameters
+// and a permutation of the set [0, n - 1]:
+// 1. Introduces a new function type that is the same as the original
+// function's type but with the order of arguments permuted
+// (only add this if it doesn't already exist)
+// 2. Changes the type of the function to this type
+// 3. Adjusts all calls to the function so that their arguments are permuted
+class FuzzerPassPermuteFunctionParameters : public FuzzerPass {
+ public:
+ FuzzerPassPermuteFunctionParameters(
+ opt::IRContext* ir_context, FactManager* fact_manager,
+ FuzzerContext* fuzzer_context,
+ protobufs::TransformationSequence* transformations);
+
+ ~FuzzerPassPermuteFunctionParameters();
+
+ void Apply() override;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_FUZZER_PASS_PERMUTE_FUNCTION_PARAMETERS_H_
diff --git a/source/fuzz/fuzzer_util.cpp b/source/fuzz/fuzzer_util.cpp
index 26961c8..4bfa195 100644
--- a/source/fuzz/fuzzer_util.cpp
+++ b/source/fuzz/fuzzer_util.cpp
@@ -395,6 +395,12 @@
return 0;
}
+opt::Instruction* GetFunctionType(opt::IRContext* context,
+ const opt::Function* function) {
+ uint32_t type_id = function->DefInst().GetSingleWordInOperand(1);
+ return context->get_def_use_mgr()->GetDef(type_id);
+}
+
opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id) {
for (auto& function : *ir_context->module()) {
if (function.result_id() == function_id) {
@@ -404,6 +410,15 @@
return nullptr;
}
+bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id) {
+ for (auto& entry_point : context->module()->entry_points()) {
+ if (entry_point.GetSingleWordInOperand(1) == function_id) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool IdIsAvailableAtUse(opt::IRContext* context,
opt::Instruction* use_instruction,
uint32_t use_input_operand_index, uint32_t id) {
diff --git a/source/fuzz/fuzzer_util.h b/source/fuzz/fuzzer_util.h
index daa836c..ddc0d5a 100644
--- a/source/fuzz/fuzzer_util.h
+++ b/source/fuzz/fuzzer_util.h
@@ -152,10 +152,20 @@
uint32_t FindFunctionType(opt::IRContext* ir_context,
const std::vector<uint32_t>& type_ids);
+// Returns a type instruction (OpTypeFunction) for |function|.
+// Returns |nullptr| if type is not found.
+opt::Instruction* GetFunctionType(opt::IRContext* context,
+ const opt::Function* function);
+
// Returns the function with result id |function_id|, or |nullptr| if no such
// function exists.
opt::Function* FindFunction(opt::IRContext* ir_context, uint32_t function_id);
+// Returns |true| if one of entry points has function id |function_id|.
+// TODO(https://github.com/KhronosGroup/SPIRV-Tools/issues/3218):
+// TransformationAddFunctionCall also has this functionality as a static method
+bool FunctionIsEntryPoint(opt::IRContext* context, uint32_t function_id);
+
// Checks whether |id| is available (according to dominance rules) at the use
// point defined by input operand |use_input_operand_index| of
// |use_instruction|.
diff --git a/source/fuzz/protobufs/spvtoolsfuzz.proto b/source/fuzz/protobufs/spvtoolsfuzz.proto
index 25f0142..83dd2cf 100644
--- a/source/fuzz/protobufs/spvtoolsfuzz.proto
+++ b/source/fuzz/protobufs/spvtoolsfuzz.proto
@@ -370,6 +370,7 @@
TransformationAccessChain access_chain = 39;
TransformationEquationInstruction equation_instruction = 40;
TransformationSwapCommutableOperands swap_commutable_operands = 41;
+ TransformationPermuteFunctionParameters permute_function_parameters = 42;
// Add additional option using the next available number.
}
}
@@ -907,6 +908,36 @@
}
+message TransformationPermuteFunctionParameters {
+
+ // A transformation that, given a non-entry-point function taking n
+ // parameters and a permutation of the set [0, n-1]:
+ // - Introduces a new function type that is the same as the original
+ // function's type but with the order of arguments permuted
+ // (only if it doesn't already exist)
+ // - Changes the type of the function to this type
+ // - Adjusts all calls to the function so that their arguments are permuted
+
+ // Function, whose parameters will be permuted
+ uint32 function_id = 1;
+
+ // |new_type_id| is a result id of a valid OpTypeFunction instruction.
+ // New type is valid if:
+ // - it has the same number of operands as the old one
+ // - function's result type is the same as the old one
+ // - function's arguments are permuted according to |permutation| vector
+ uint32 new_type_id = 2;
+
+ // An array of size |n|, where |n| is a number of arguments to a function
+ // with |function_id|. For each i: 0 <= permutation[i] < n.
+ //
+ // i-th element of this array contains a position for an i-th
+ // function's argument (i.e. i-th argument will be permutation[i]-th
+ // after running this transformation)
+ repeated uint32 permutation = 3;
+
+}
+
message TransformationReplaceBooleanConstantWithConstantBinary {
// A transformation to capture replacing a use of a boolean constant with
diff --git a/source/fuzz/transformation.cpp b/source/fuzz/transformation.cpp
index fe0d41c..e06999c 100644
--- a/source/fuzz/transformation.cpp
+++ b/source/fuzz/transformation.cpp
@@ -47,6 +47,7 @@
#include "source/fuzz/transformation_merge_blocks.h"
#include "source/fuzz/transformation_move_block_down.h"
#include "source/fuzz/transformation_outline_function.h"
+#include "source/fuzz/transformation_permute_function_parameters.h"
#include "source/fuzz/transformation_replace_boolean_constant_with_constant_binary.h"
#include "source/fuzz/transformation_replace_constant_with_uniform.h"
#include "source/fuzz/transformation_replace_id_with_synonym.h"
@@ -145,6 +146,10 @@
return MakeUnique<TransformationOutlineFunction>(
message.outline_function());
case protobufs::Transformation::TransformationCase::
+ kPermuteFunctionParameters:
+ return MakeUnique<TransformationPermuteFunctionParameters>(
+ message.permute_function_parameters());
+ case protobufs::Transformation::TransformationCase::
kReplaceBooleanConstantWithConstantBinary:
return MakeUnique<TransformationReplaceBooleanConstantWithConstantBinary>(
message.replace_boolean_constant_with_constant_binary());
diff --git a/source/fuzz/transformation_add_type_function.h b/source/fuzz/transformation_add_type_function.h
index 2b59661..3880963 100644
--- a/source/fuzz/transformation_add_type_function.h
+++ b/source/fuzz/transformation_add_type_function.h
@@ -37,7 +37,7 @@
// - |message_.return_type_id| and each element of |message_.argument_type_id|
// must be the ids of non-function types
// - The module must not contain an OpTypeFunction instruction defining a
- // function type with the signature provided by teh given return and
+ // function type with the signature provided by the given return and
// argument types
bool IsApplicable(opt::IRContext* context,
const FactManager& fact_manager) const override;
diff --git a/source/fuzz/transformation_permute_function_parameters.cpp b/source/fuzz/transformation_permute_function_parameters.cpp
new file mode 100644
index 0000000..2141533
--- /dev/null
+++ b/source/fuzz/transformation_permute_function_parameters.cpp
@@ -0,0 +1,184 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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 <unordered_set>
+#include <vector>
+
+#include "source/fuzz/fuzzer_util.h"
+#include "source/fuzz/transformation_permute_function_parameters.h"
+
+namespace spvtools {
+namespace fuzz {
+
+TransformationPermuteFunctionParameters::
+ TransformationPermuteFunctionParameters(
+ const spvtools::fuzz::protobufs::
+ TransformationPermuteFunctionParameters& message)
+ : message_(message) {}
+
+TransformationPermuteFunctionParameters::
+ TransformationPermuteFunctionParameters(
+ uint32_t function_id, uint32_t new_type_id,
+ const std::vector<uint32_t>& permutation) {
+ message_.set_function_id(function_id);
+ message_.set_new_type_id(new_type_id);
+
+ for (auto index : permutation) {
+ message_.add_permutation(index);
+ }
+}
+
+bool TransformationPermuteFunctionParameters::IsApplicable(
+ opt::IRContext* context, const FactManager& /*unused*/) const {
+ // Check that function exists
+ const auto* function =
+ fuzzerutil::FindFunction(context, message_.function_id());
+ if (!function || function->DefInst().opcode() != SpvOpFunction ||
+ fuzzerutil::FunctionIsEntryPoint(context, function->result_id())) {
+ return false;
+ }
+
+ // Check that permutation has valid indices
+ const auto* function_type = fuzzerutil::GetFunctionType(context, function);
+ assert(function_type && "Function type is null");
+
+ const auto& permutation = message_.permutation();
+
+ // Don't take return type into account
+ auto arg_size = function_type->NumInOperands() - 1;
+
+ // |permutation| vector should be equal to the number of arguments
+ if (static_cast<uint32_t>(permutation.size()) != arg_size) {
+ return false;
+ }
+
+ // Check that all indices are valid
+ // and unique integers from the [0, n-1] set
+ std::unordered_set<uint32_t> unique_indices;
+ for (auto index : permutation) {
+ // We don't compare |index| with 0 since it's an unsigned integer
+ if (index >= arg_size) {
+ return false;
+ }
+
+ unique_indices.insert(index);
+ }
+
+ // Check that permutation doesn't have duplicated values
+ assert(unique_indices.size() == arg_size && "Permutation has duplicates");
+
+ // Check that new function's type is valid:
+ // - Has the same number of operands
+ // - Has the same result type as the old one
+ // - Order of arguments is permuted
+ auto new_type_id = message_.new_type_id();
+ const auto* new_type = context->get_def_use_mgr()->GetDef(new_type_id);
+
+ if (!new_type || new_type->opcode() != SpvOpTypeFunction ||
+ new_type->NumInOperands() != function_type->NumInOperands()) {
+ return false;
+ }
+
+ // Check that both instructions have the same result type
+ if (new_type->GetSingleWordInOperand(0) !=
+ function_type->GetSingleWordInOperand(0)) {
+ return false;
+ }
+
+ // Check that new function type has its arguments permuted
+ for (int i = 0, n = static_cast<int>(permutation.size()); i < n; ++i) {
+ // +1 to take return type into account
+ if (new_type->GetSingleWordInOperand(i + 1) !=
+ function_type->GetSingleWordInOperand(permutation[i] + 1)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void TransformationPermuteFunctionParameters::Apply(
+ opt::IRContext* context, FactManager* /*unused*/) const {
+ // Retrieve all data from the message
+ uint32_t function_id = message_.function_id();
+ uint32_t new_type_id = message_.new_type_id();
+ const auto& permutation = message_.permutation();
+
+ // Find the function that will be transformed
+ auto* function = fuzzerutil::FindFunction(context, function_id);
+ assert(function && "Can't find the function");
+
+ // Change function's type
+ function->DefInst().SetInOperand(1, {new_type_id});
+
+ // Adjust OpFunctionParameter instructions
+
+ // Collect ids and types from OpFunctionParameter instructions
+ std::vector<uint32_t> param_id, param_type;
+ function->ForEachParam(
+ [¶m_id, ¶m_type](const opt::Instruction* param) {
+ param_id.push_back(param->result_id());
+ param_type.push_back(param->type_id());
+ });
+
+ // Permute parameters' ids and types
+ std::vector<uint32_t> permuted_param_id, permuted_param_type;
+ for (auto index : permutation) {
+ permuted_param_id.push_back(param_id[index]);
+ permuted_param_type.push_back(param_type[index]);
+ }
+
+ // Set OpFunctionParameter instructions to point to new parameters
+ size_t i = 0;
+ function->ForEachParam(
+ [&i, &permuted_param_id, &permuted_param_type](opt::Instruction* param) {
+ param->SetResultType(permuted_param_type[i]);
+ param->SetResultId(permuted_param_id[i]);
+ ++i;
+ });
+
+ // Fix all OpFunctionCall instructions
+ context->get_def_use_mgr()->ForEachUser(
+ &function->DefInst(),
+ [function_id, &permutation](opt::Instruction* call) {
+ if (call->opcode() != SpvOpFunctionCall ||
+ call->GetSingleWordInOperand(0) != function_id) {
+ return;
+ }
+
+ opt::Instruction::OperandList call_operands = {
+ call->GetInOperand(0) // Function id
+ };
+
+ for (auto index : permutation) {
+ // Take function id into account
+ call_operands.push_back(call->GetInOperand(index + 1));
+ }
+
+ call->SetInOperands(std::move(call_operands));
+ });
+
+ // Make sure our changes are analyzed
+ context->InvalidateAnalysesExceptFor(opt::IRContext::Analysis::kAnalysisNone);
+}
+
+protobufs::Transformation TransformationPermuteFunctionParameters::ToMessage()
+ const {
+ protobufs::Transformation result;
+ *result.mutable_permute_function_parameters() = message_;
+ return result;
+}
+
+} // namespace fuzz
+} // namespace spvtools
diff --git a/source/fuzz/transformation_permute_function_parameters.h b/source/fuzz/transformation_permute_function_parameters.h
new file mode 100644
index 0000000..c67a735
--- /dev/null
+++ b/source/fuzz/transformation_permute_function_parameters.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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 SOURCE_FUZZ_TRANSFORMATION_PERMUTE_FUNCTION_PARAMETERS_H_
+#define SOURCE_FUZZ_TRANSFORMATION_PERMUTE_FUNCTION_PARAMETERS_H_
+
+#include "source/fuzz/fact_manager.h"
+#include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
+#include "source/fuzz/transformation.h"
+#include "source/opt/ir_context.h"
+
+namespace spvtools {
+namespace fuzz {
+
+class TransformationPermuteFunctionParameters : public Transformation {
+ public:
+ explicit TransformationPermuteFunctionParameters(
+ const protobufs::TransformationPermuteFunctionParameters& message);
+
+ TransformationPermuteFunctionParameters(
+ uint32_t function_id, uint32_t new_type_id,
+ const std::vector<uint32_t>& permutation);
+
+ // - |function_id| is a valid non-entry-point OpFunction instruction
+ // - |new_type_id| is a result id of a valid OpTypeFunction instruction.
+ // New type is valid if:
+ // - it has the same number of operands as the old one
+ // - function's result type is the same as the old one
+ // - function's arguments are permuted according to |permutation| vector
+ // - |permutation| is a set of [0..(n - 1)], where n is a number of arguments
+ // to the function
+ bool IsApplicable(opt::IRContext* context,
+ const FactManager& fact_manager) const override;
+
+ // - OpFunction instruction with |result_id == function_id| is changed.
+ // Its arguments are permuted according to the |permutation| vector
+ // - Changed function gets a new type specified by |type_id|
+ // - Calls to the function are adjusted accordingly
+ void Apply(opt::IRContext* context, FactManager* fact_manager) const override;
+
+ protobufs::Transformation ToMessage() const override;
+
+ private:
+ protobufs::TransformationPermuteFunctionParameters message_;
+};
+
+} // namespace fuzz
+} // namespace spvtools
+
+#endif // SOURCE_FUZZ_TRANSFORMATION_PERMUTE_FUNCTION_PARAMETERS_H_
diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt
index 40aa152..1e26e74 100644
--- a/test/fuzz/CMakeLists.txt
+++ b/test/fuzz/CMakeLists.txt
@@ -54,6 +54,7 @@
transformation_merge_blocks_test.cpp
transformation_move_block_down_test.cpp
transformation_outline_function_test.cpp
+ transformation_permute_function_parameters_test.cpp
transformation_replace_boolean_constant_with_constant_binary_test.cpp
transformation_replace_constant_with_uniform_test.cpp
transformation_replace_id_with_synonym_test.cpp
diff --git a/test/fuzz/transformation_permute_function_parameters_test.cpp b/test/fuzz/transformation_permute_function_parameters_test.cpp
new file mode 100644
index 0000000..1af4699
--- /dev/null
+++ b/test/fuzz/transformation_permute_function_parameters_test.cpp
@@ -0,0 +1,430 @@
+// Copyright (c) 2020 Vasyl Teliman
+//
+// 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 "source/fuzz/transformation_permute_function_parameters.h"
+#include "test/fuzz/fuzz_test_util.h"
+
+namespace spvtools {
+namespace fuzz {
+namespace {
+
+TEST(TransformationPermuteFunctionParametersTest, BasicTest) {
+ std::string shader = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %72 %74
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %12 "g(f1;f1;"
+ OpName %10 "x"
+ OpName %11 "y"
+ OpName %22 "f(f1;i1;vf2;"
+ OpName %19 "x"
+ OpName %20 "y"
+ OpName %21 "z"
+ OpName %28 "cond(i1;f1;"
+ OpName %26 "a"
+ OpName %27 "b"
+ OpName %53 "param"
+ OpName %54 "param"
+ OpName %66 "param"
+ OpName %67 "param"
+ OpName %72 "color"
+ OpName %74 "gl_FragCoord"
+ OpName %75 "param"
+ OpName %79 "param"
+ OpName %85 "param"
+ OpName %86 "param"
+ OpName %91 "param"
+ OpName %92 "param"
+ OpName %93 "param"
+ OpName %99 "param"
+ OpName %100 "param"
+ OpName %101 "param"
+ OpDecorate %20 RelaxedPrecision
+ OpDecorate %26 RelaxedPrecision
+ OpDecorate %47 RelaxedPrecision
+ OpDecorate %58 RelaxedPrecision
+ OpDecorate %72 Location 0
+ OpDecorate %74 BuiltIn FragCoord
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeVector %6 4
+ %9 = OpTypeFunction %8 %7 %7
+ %14 = OpTypeInt 32 1
+ %15 = OpTypePointer Function %14
+ %16 = OpTypeVector %6 2
+ %17 = OpTypePointer Function %16
+ %18 = OpTypeFunction %8 %7 %15 %17
+ %24 = OpTypeBool
+ %25 = OpTypeFunction %24 %15 %7
+ %105 = OpTypeFunction %24 %7 %15 ; predefined type for %28
+ %31 = OpConstant %6 255
+ %33 = OpConstant %6 0
+ %34 = OpConstant %6 1
+ %42 = OpTypeInt 32 0
+ %43 = OpConstant %42 0
+ %49 = OpConstant %42 1
+ %64 = OpConstant %14 4
+ %65 = OpConstant %6 5
+ %71 = OpTypePointer Output %8
+ %72 = OpVariable %71 Output
+ %73 = OpTypePointer Input %8
+ %74 = OpVariable %73 Input
+ %76 = OpTypePointer Input %6
+ %84 = OpConstant %14 5
+ %90 = OpConstant %6 3
+ %98 = OpConstant %6 4
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %66 = OpVariable %15 Function
+ %67 = OpVariable %7 Function
+ %75 = OpVariable %7 Function
+ %79 = OpVariable %7 Function
+ %85 = OpVariable %15 Function
+ %86 = OpVariable %7 Function
+ %91 = OpVariable %7 Function
+ %92 = OpVariable %15 Function
+ %93 = OpVariable %17 Function
+ %99 = OpVariable %7 Function
+ %100 = OpVariable %15 Function
+ %101 = OpVariable %17 Function
+ OpStore %66 %64
+ OpStore %67 %65
+ %68 = OpFunctionCall %24 %28 %66 %67
+ OpSelectionMerge %70 None
+ OpBranchConditional %68 %69 %83
+ %69 = OpLabel
+ %77 = OpAccessChain %76 %74 %43
+ %78 = OpLoad %6 %77
+ OpStore %75 %78
+ %80 = OpAccessChain %76 %74 %49
+ %81 = OpLoad %6 %80
+ OpStore %79 %81
+ %82 = OpFunctionCall %8 %12 %75 %79
+ OpStore %72 %82
+ OpBranch %70
+ %83 = OpLabel
+ OpStore %85 %84
+ OpStore %86 %65
+ %87 = OpFunctionCall %24 %28 %85 %86
+ OpSelectionMerge %89 None
+ OpBranchConditional %87 %88 %97
+ %88 = OpLabel
+ OpStore %91 %90
+ OpStore %92 %64
+ %94 = OpLoad %8 %74
+ %95 = OpVectorShuffle %16 %94 %94 0 1
+ OpStore %93 %95
+ %96 = OpFunctionCall %8 %22 %91 %92 %93
+ OpStore %72 %96
+ OpBranch %89
+ %97 = OpLabel
+ OpStore %99 %98
+ OpStore %100 %84
+ %102 = OpLoad %8 %74
+ %103 = OpVectorShuffle %16 %102 %102 0 1
+ OpStore %101 %103
+ %104 = OpFunctionCall %8 %22 %99 %100 %101
+ OpStore %72 %104
+ OpBranch %89
+ %89 = OpLabel
+ OpBranch %70
+ %70 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %8 None %9
+ %10 = OpFunctionParameter %7
+ %11 = OpFunctionParameter %7
+ %13 = OpLabel
+ %30 = OpLoad %6 %10
+ %32 = OpFDiv %6 %30 %31
+ %35 = OpLoad %6 %11
+ %36 = OpFDiv %6 %35 %31
+ %37 = OpFSub %6 %34 %36
+ %38 = OpCompositeConstruct %8 %32 %33 %37 %34
+ OpReturnValue %38
+ OpFunctionEnd
+ %22 = OpFunction %8 None %18
+ %19 = OpFunctionParameter %7
+ %20 = OpFunctionParameter %15
+ %21 = OpFunctionParameter %17
+ %23 = OpLabel
+ %53 = OpVariable %7 Function
+ %54 = OpVariable %7 Function
+ %41 = OpLoad %6 %19
+ %44 = OpAccessChain %7 %21 %43
+ %45 = OpLoad %6 %44
+ %46 = OpFAdd %6 %41 %45
+ %47 = OpLoad %14 %20
+ %48 = OpConvertSToF %6 %47
+ %50 = OpAccessChain %7 %21 %49
+ %51 = OpLoad %6 %50
+ %52 = OpFAdd %6 %48 %51
+ OpStore %53 %46
+ OpStore %54 %52
+ %55 = OpFunctionCall %8 %12 %53 %54
+ OpReturnValue %55
+ OpFunctionEnd
+ %28 = OpFunction %24 None %25
+ %26 = OpFunctionParameter %15
+ %27 = OpFunctionParameter %7
+ %29 = OpLabel
+ %58 = OpLoad %14 %26
+ %59 = OpConvertSToF %6 %58
+ %60 = OpLoad %6 %27
+ %61 = OpFOrdLessThan %24 %59 %60
+ OpReturnValue %61
+ OpFunctionEnd
+
+ )";
+
+ const auto env = SPV_ENV_UNIVERSAL_1_3;
+ const auto consumer = nullptr;
+ const auto context = BuildModule(env, consumer, shader, kFuzzAssembleOption);
+ ASSERT_TRUE(IsValid(env, context.get()));
+
+ FactManager fact_manager;
+
+ // Can't permute main function
+ ASSERT_FALSE(TransformationPermuteFunctionParameters(4, 0, {}).IsApplicable(
+ context.get(), fact_manager));
+
+ // Can't permute invalid instruction
+ ASSERT_FALSE(TransformationPermuteFunctionParameters(101, 0, {})
+ .IsApplicable(context.get(), fact_manager));
+
+ // Permutation has too many values
+ ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {2, 1, 0, 3})
+ .IsApplicable(context.get(), fact_manager));
+
+ // Permutation has too few values
+ ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {0, 1})
+ .IsApplicable(context.get(), fact_manager));
+
+ // Permutation has invalid values
+ ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 0, {3, 1, 0})
+ .IsApplicable(context.get(), fact_manager));
+
+ // Type id is not an OpTypeFunction instruction
+ ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 42, {2, 1, 0})
+ .IsApplicable(context.get(), fact_manager));
+
+ // Type id has incorrect number of operands
+ ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 9, {2, 1, 0})
+ .IsApplicable(context.get(), fact_manager));
+
+ // OpTypeFunction has operands out of order
+ ASSERT_FALSE(TransformationPermuteFunctionParameters(22, 18, {2, 1, 0})
+ .IsApplicable(context.get(), fact_manager));
+
+ // Successful transformations
+ {
+ // Function has two operands of the same type:
+ // initial OpTypeFunction should be enough
+ TransformationPermuteFunctionParameters transformation(12, 9, {1, 0});
+ ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+ transformation.Apply(context.get(), &fact_manager);
+ ASSERT_TRUE(IsValid(env, context.get()));
+ }
+ {
+ TransformationPermuteFunctionParameters transformation(28, 105, {1, 0});
+ ASSERT_TRUE(transformation.IsApplicable(context.get(), fact_manager));
+ transformation.Apply(context.get(), &fact_manager);
+ ASSERT_TRUE(IsValid(env, context.get()));
+ }
+
+ std::string after_transformation = R"(
+ OpCapability Shader
+ %1 = OpExtInstImport "GLSL.std.450"
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %4 "main" %72 %74
+ OpExecutionMode %4 OriginUpperLeft
+ OpSource ESSL 310
+ OpName %4 "main"
+ OpName %12 "g(f1;f1;"
+ OpName %10 "x"
+ OpName %11 "y"
+ OpName %22 "f(f1;i1;vf2;"
+ OpName %19 "x"
+ OpName %20 "y"
+ OpName %21 "z"
+ OpName %28 "cond(i1;f1;"
+ OpName %26 "a"
+ OpName %27 "b"
+ OpName %53 "param"
+ OpName %54 "param"
+ OpName %66 "param"
+ OpName %67 "param"
+ OpName %72 "color"
+ OpName %74 "gl_FragCoord"
+ OpName %75 "param"
+ OpName %79 "param"
+ OpName %85 "param"
+ OpName %86 "param"
+ OpName %91 "param"
+ OpName %92 "param"
+ OpName %93 "param"
+ OpName %99 "param"
+ OpName %100 "param"
+ OpName %101 "param"
+ OpDecorate %20 RelaxedPrecision
+ OpDecorate %26 RelaxedPrecision
+ OpDecorate %47 RelaxedPrecision
+ OpDecorate %58 RelaxedPrecision
+ OpDecorate %72 Location 0
+ OpDecorate %74 BuiltIn FragCoord
+ %2 = OpTypeVoid
+ %3 = OpTypeFunction %2
+ %6 = OpTypeFloat 32
+ %7 = OpTypePointer Function %6
+ %8 = OpTypeVector %6 4
+ %9 = OpTypeFunction %8 %7 %7
+ %14 = OpTypeInt 32 1
+ %15 = OpTypePointer Function %14
+ %16 = OpTypeVector %6 2
+ %17 = OpTypePointer Function %16
+ %18 = OpTypeFunction %8 %7 %15 %17
+ %24 = OpTypeBool
+ %25 = OpTypeFunction %24 %15 %7
+ %105 = OpTypeFunction %24 %7 %15 ; predefined type for %28
+ %31 = OpConstant %6 255
+ %33 = OpConstant %6 0
+ %34 = OpConstant %6 1
+ %42 = OpTypeInt 32 0
+ %43 = OpConstant %42 0
+ %49 = OpConstant %42 1
+ %64 = OpConstant %14 4
+ %65 = OpConstant %6 5
+ %71 = OpTypePointer Output %8
+ %72 = OpVariable %71 Output
+ %73 = OpTypePointer Input %8
+ %74 = OpVariable %73 Input
+ %76 = OpTypePointer Input %6
+ %84 = OpConstant %14 5
+ %90 = OpConstant %6 3
+ %98 = OpConstant %6 4
+ %4 = OpFunction %2 None %3
+ %5 = OpLabel
+ %66 = OpVariable %15 Function
+ %67 = OpVariable %7 Function
+ %75 = OpVariable %7 Function
+ %79 = OpVariable %7 Function
+ %85 = OpVariable %15 Function
+ %86 = OpVariable %7 Function
+ %91 = OpVariable %7 Function
+ %92 = OpVariable %15 Function
+ %93 = OpVariable %17 Function
+ %99 = OpVariable %7 Function
+ %100 = OpVariable %15 Function
+ %101 = OpVariable %17 Function
+ OpStore %66 %64
+ OpStore %67 %65
+ %68 = OpFunctionCall %24 %28 %67 %66
+ OpSelectionMerge %70 None
+ OpBranchConditional %68 %69 %83
+ %69 = OpLabel
+ %77 = OpAccessChain %76 %74 %43
+ %78 = OpLoad %6 %77
+ OpStore %75 %78
+ %80 = OpAccessChain %76 %74 %49
+ %81 = OpLoad %6 %80
+ OpStore %79 %81
+ %82 = OpFunctionCall %8 %12 %79 %75
+ OpStore %72 %82
+ OpBranch %70
+ %83 = OpLabel
+ OpStore %85 %84
+ OpStore %86 %65
+ %87 = OpFunctionCall %24 %28 %86 %85
+ OpSelectionMerge %89 None
+ OpBranchConditional %87 %88 %97
+ %88 = OpLabel
+ OpStore %91 %90
+ OpStore %92 %64
+ %94 = OpLoad %8 %74
+ %95 = OpVectorShuffle %16 %94 %94 0 1
+ OpStore %93 %95
+ %96 = OpFunctionCall %8 %22 %91 %92 %93
+ OpStore %72 %96
+ OpBranch %89
+ %97 = OpLabel
+ OpStore %99 %98
+ OpStore %100 %84
+ %102 = OpLoad %8 %74
+ %103 = OpVectorShuffle %16 %102 %102 0 1
+ OpStore %101 %103
+ %104 = OpFunctionCall %8 %22 %99 %100 %101
+ OpStore %72 %104
+ OpBranch %89
+ %89 = OpLabel
+ OpBranch %70
+ %70 = OpLabel
+ OpReturn
+ OpFunctionEnd
+ %12 = OpFunction %8 None %9
+ %11 = OpFunctionParameter %7
+ %10 = OpFunctionParameter %7
+ %13 = OpLabel
+ %30 = OpLoad %6 %10
+ %32 = OpFDiv %6 %30 %31
+ %35 = OpLoad %6 %11
+ %36 = OpFDiv %6 %35 %31
+ %37 = OpFSub %6 %34 %36
+ %38 = OpCompositeConstruct %8 %32 %33 %37 %34
+ OpReturnValue %38
+ OpFunctionEnd
+ %22 = OpFunction %8 None %18
+ %19 = OpFunctionParameter %7
+ %20 = OpFunctionParameter %15
+ %21 = OpFunctionParameter %17
+ %23 = OpLabel
+ %53 = OpVariable %7 Function
+ %54 = OpVariable %7 Function
+ %41 = OpLoad %6 %19
+ %44 = OpAccessChain %7 %21 %43
+ %45 = OpLoad %6 %44
+ %46 = OpFAdd %6 %41 %45
+ %47 = OpLoad %14 %20
+ %48 = OpConvertSToF %6 %47
+ %50 = OpAccessChain %7 %21 %49
+ %51 = OpLoad %6 %50
+ %52 = OpFAdd %6 %48 %51
+ OpStore %53 %46
+ OpStore %54 %52
+ %55 = OpFunctionCall %8 %12 %54 %53
+ OpReturnValue %55
+ OpFunctionEnd
+ %28 = OpFunction %24 None %105
+ %27 = OpFunctionParameter %7
+ %26 = OpFunctionParameter %15
+ %29 = OpLabel
+ %58 = OpLoad %14 %26
+ %59 = OpConvertSToF %6 %58
+ %60 = OpLoad %6 %27
+ %61 = OpFOrdLessThan %24 %59 %60
+ OpReturnValue %61
+ OpFunctionEnd
+ )";
+
+ ASSERT_TRUE(IsEqual(env, after_transformation, context.get()));
+}
+
+} // namespace
+} // namespace fuzz
+} // namespace spvtools
diff --git a/utils/check_copyright.py b/utils/check_copyright.py
index 1e8faf0..b228300 100755
--- a/utils/check_copyright.py
+++ b/utils/check_copyright.py
@@ -33,7 +33,8 @@
'Google LLC',
'Pierre Moreau',
'Samsung Inc',
- 'André Perez Maselco']
+ 'André Perez Maselco',
+ 'Vasyl Teliman']
CURRENT_YEAR='2020'
YEARS = '(2014-2016|2015-2016|2016|2016-2017|2017|2017-2019|2018|2019|2020)'