| /* |
| * Copyright 2020 Google LLC |
| * |
| * 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 "tools/fuzzer/expr_gen.h" |
| |
| #include <algorithm> |
| #include <array> |
| #include <cassert> |
| #include <optional> |
| #include <random> |
| #include <type_traits> |
| #include <unordered_map> |
| #include <unordered_set> |
| #include <variant> |
| |
| #include "lldb-eval/defines.h" |
| #include "tools/fuzzer/ast.h" |
| #include "tools/fuzzer/constraints.h" |
| #include "tools/fuzzer/enum_bitset.h" |
| |
| namespace fuzzer { |
| |
| class Weights { |
| public: |
| using ExprWeightsArray = std::array<float, NUM_GEN_EXPR_KINDS>; |
| using TypeWeightsArray = std::array<float, NUM_GEN_TYPE_KINDS>; |
| |
| ExprWeightsArray& expr_weights() { return expr_weights_; } |
| const ExprWeightsArray& expr_weights() const { return expr_weights_; } |
| |
| TypeWeightsArray& type_weights() { return type_weights_; } |
| const TypeWeightsArray& type_weights() const { return type_weights_; } |
| |
| float& operator[](ExprKind kind) { return expr_weights_[(size_t)kind]; } |
| float& operator[](TypeKind kind) { return type_weights_[(size_t)kind]; } |
| |
| const float& operator[](ExprKind kind) const { |
| return expr_weights_[(size_t)kind]; |
| } |
| const float& operator[](TypeKind kind) const { |
| return type_weights_[(size_t)kind]; |
| } |
| |
| private: |
| std::array<float, NUM_GEN_EXPR_KINDS> expr_weights_; |
| std::array<float, NUM_GEN_TYPE_KINDS> type_weights_; |
| }; |
| |
| using ScalarMask = EnumBitset<ScalarType>; |
| |
| int expr_precedence(const Expr& e) { |
| return std::visit([](const auto& e) { return e.precedence(); }, e); |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_boolean_constant( |
| const ExprConstraints& constraints) { |
| const auto& type_constraints = constraints.type_constraints(); |
| |
| if (constraints.must_be_lvalue() || |
| !type_constraints.allows_any_of(INT_TYPES | FLOAT_TYPES)) { |
| return {}; |
| } |
| |
| return BooleanConstant(rng_->gen_boolean()); |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_integer_constant( |
| const ExprConstraints& constraints) { |
| if (constraints.must_be_lvalue()) { |
| return {}; |
| } |
| |
| const auto& type_constraints = constraints.type_constraints(); |
| |
| // Integers can be generated in place of floats |
| if (type_constraints.allows_any_of(INT_TYPES | FLOAT_TYPES)) { |
| return rng_->gen_integer_constant(cfg_.int_const_min, cfg_.int_const_max); |
| } |
| |
| if (type_constraints.allows_void_pointer()) { |
| return IntegerConstant(0); |
| } |
| |
| return {}; |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_double_constant( |
| const ExprConstraints& constraints) { |
| if (constraints.must_be_lvalue()) { |
| return {}; |
| } |
| |
| const auto& type_constraints = constraints.type_constraints(); |
| if (type_constraints.allows_any_of(FLOAT_TYPES)) { |
| return rng_->gen_double_constant(cfg_.double_constant_min, |
| cfg_.double_constant_max); |
| } |
| |
| return {}; |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_variable_expr( |
| const ExprConstraints& constraints) { |
| // TODO: This is a stub, which is addressed by the `fuzzer-ptrs` branch |
| const auto& type_constraints = constraints.type_constraints(); |
| |
| for (const auto& type : cfg_.symbol_table) { |
| if (!type_constraints.allows_type(type.first)) { |
| continue; |
| } |
| |
| if (type.second.empty()) { |
| continue; |
| } |
| |
| return VariableExpr(type.second[0]); |
| } |
| |
| return {}; |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_binary_expr( |
| const Weights& weights, const ExprConstraints& constraints) { |
| if (constraints.must_be_lvalue()) { |
| return {}; |
| } |
| |
| const auto& type_constraints = constraints.type_constraints(); |
| |
| BinOpMask mask = cfg_.bin_op_mask; |
| |
| ScalarMask default_type_mask; |
| if (type_constraints.allows_any_of(INT_TYPES)) { |
| default_type_mask |= INT_TYPES; |
| } |
| if (type_constraints.allows_any_of(FLOAT_TYPES)) { |
| default_type_mask |= FLOAT_TYPES; |
| } |
| |
| if (default_type_mask.none()) { |
| constexpr BinOpMask PTR_OPS = {BinOp::Plus, BinOp::Minus}; |
| mask &= PTR_OPS; |
| } |
| |
| while (mask.any()) { |
| auto op = rng_->gen_bin_op(mask); |
| |
| SpecificTypes lhs_types; |
| SpecificTypes rhs_types; |
| |
| switch (op) { |
| case BinOp::Mult: |
| case BinOp::Div: |
| lhs_types = default_type_mask; |
| rhs_types = default_type_mask; |
| break; |
| |
| case BinOp::BitAnd: |
| case BinOp::BitOr: |
| case BinOp::BitXor: |
| case BinOp::Shl: |
| case BinOp::Shr: |
| case BinOp::Mod: |
| lhs_types = INT_TYPES; |
| rhs_types = INT_TYPES; |
| break; |
| |
| case BinOp::LogicalAnd: |
| case BinOp::LogicalOr: |
| lhs_types = SpecificTypes::all_in_bool_ctx(); |
| rhs_types = SpecificTypes::all_in_bool_ctx(); |
| break; |
| |
| case BinOp::Eq: |
| case BinOp::Ne: |
| case BinOp::Lt: |
| case BinOp::Le: |
| case BinOp::Gt: |
| case BinOp::Ge: { |
| lhs_types = INT_TYPES | FLOAT_TYPES; |
| rhs_types = INT_TYPES | FLOAT_TYPES; |
| |
| // Try and see if we can generate a pointer type. If not, we'll just |
| // compare scalars. |
| bool gen_ptr_exprs = |
| rng_->gen_binop_ptr_expr(cfg_.binop_gen_ptr_expr_prob); |
| if (gen_ptr_exprs) { |
| auto maybe_type = |
| gen_type(weights, SpecificTypes::make_any_pointer_constraints()); |
| if (maybe_type.has_value()) { |
| const auto& type = maybe_type.value(); |
| lhs_types = SpecificTypes(type); |
| rhs_types = SpecificTypes(type); |
| } |
| } |
| } break; |
| |
| case BinOp::Plus: |
| case BinOp::Minus: { |
| bool allows_scalars = default_type_mask.any(); |
| bool allows_pointers = type_constraints.allows_pointer(); |
| |
| if (allows_scalars && allows_pointers) { |
| if (rng_->gen_binop_ptr_expr(cfg_.binop_gen_ptr_expr_prob)) { |
| allows_scalars = false; |
| } else { |
| allows_pointers = false; |
| } |
| } |
| |
| if (!allows_scalars && !allows_pointers) { |
| mask[op] = false; |
| continue; |
| } |
| |
| if (allows_pointers) { |
| auto maybe_type = gen_type(weights, type_constraints); |
| if (!maybe_type.has_value()) { |
| mask[op] = false; |
| continue; |
| } |
| const auto& type = maybe_type.value(); |
| const auto* ptr_type = std::get_if<PointerType>(&type); |
| if (ptr_type == nullptr) { |
| mask[op] = false; |
| continue; |
| } |
| const auto* scalar_type = |
| std::get_if<ScalarType>(&ptr_type->type().type()); |
| if (scalar_type != nullptr && *scalar_type == ScalarType::Void) { |
| mask[op] = false; |
| continue; |
| } |
| |
| lhs_types = SpecificTypes(type); |
| rhs_types = INT_TYPES; |
| |
| if (op == BinOp::Plus && |
| rng_->gen_binop_flip_operands(cfg_.binop_flip_operands_prob)) { |
| std::swap(lhs_types, rhs_types); |
| } |
| } else { |
| if (op == BinOp::Minus && |
| rng_->gen_binop_ptrdiff_expr(cfg_.binop_gen_ptrdiff_expr_prob)) { |
| auto maybe_type = gen_type(weights, type_constraints); |
| if (!maybe_type.has_value()) { |
| mask[op] = false; |
| continue; |
| } |
| const auto& type = maybe_type.value(); |
| const auto* ptr_type = std::get_if<PointerType>(&type); |
| if (ptr_type == nullptr) { |
| mask[op] = false; |
| continue; |
| } |
| const auto* scalar_type = |
| std::get_if<ScalarType>(&ptr_type->type().type()); |
| if (scalar_type != nullptr && *scalar_type == ScalarType::Void) { |
| mask[op] = false; |
| continue; |
| } |
| |
| lhs_types = SpecificTypes(type); |
| rhs_types = SpecificTypes(type); |
| } else { |
| lhs_types = default_type_mask; |
| rhs_types = default_type_mask; |
| } |
| } |
| } break; |
| |
| default: |
| lldb_eval_unreachable( |
| "Unhandled switch case, did you introduce a new binary " |
| "operator?"); |
| } |
| |
| TypeConstraints lhs_constraints = std::move(lhs_types); |
| TypeConstraints rhs_constraints = std::move(rhs_types); |
| |
| auto maybe_lhs = gen_with_weights(weights, std::move(lhs_constraints)); |
| if (!maybe_lhs.has_value()) { |
| mask[op] = false; |
| continue; |
| } |
| Expr lhs = std::move(maybe_lhs.value()); |
| |
| auto maybe_rhs = gen_with_weights(weights, std::move(rhs_constraints)); |
| if (!maybe_rhs.has_value()) { |
| mask[op] = false; |
| continue; |
| } |
| Expr rhs = std::move(maybe_rhs.value()); |
| |
| // Rules for parenthesising the left hand side: |
| // 1. If the left hand side has a strictly lower precedence than ours, |
| // then we will have to emit parens. |
| // Example: We emit `(3 + 4) * 5` instead of `3 + 4 * 5`. |
| // 2. If the left hand side has the same precedence as we do, then we |
| // don't have to emit any parens. This is because all lldb-eval |
| // binary operators have left-to-right associativity. |
| // Example: We do not have to emit `(3 - 4) + 5`, `3 - 4 + 5` will |
| // also do. |
| if (expr_precedence(lhs) > bin_op_precedence(op)) { |
| lhs = ParenthesizedExpr(std::move(lhs)); |
| } |
| |
| // Rules for parenthesising the right hand side: |
| // 1. If the right hand side has a strictly lower precedence than |
| // ours, |
| // then we will have to emit parens. |
| // Example: We emit `5 * (3 + 4)` instead of `5 * 3 + 4`. |
| // 2. If the right hand side has the same precedence as we do, then we |
| // should emit parens for good measure. This is because all |
| // lldb-eval binary operators have left-to-right associativity and |
| // we do not want to violate this with respect to the generated |
| // AST. Example: We emit `3 - (4 + 5)` instead of `3 - 4 + 5`. We |
| // also emit `3 + (4 + 5)` instead of `3 + 4 + 5`, even though both |
| // expressions are equivalent. |
| if (expr_precedence(rhs) >= bin_op_precedence(op)) { |
| rhs = ParenthesizedExpr(std::move(rhs)); |
| } |
| |
| return BinaryExpr(std::move(lhs), op, std::move(rhs)); |
| } |
| |
| return {}; |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_unary_expr( |
| const Weights& weights, const ExprConstraints& constraints) { |
| if (constraints.must_be_lvalue()) { |
| return {}; |
| } |
| |
| const auto& type_constraints = constraints.type_constraints(); |
| |
| ScalarMask default_type_mask; |
| if (type_constraints.allows_any_of(INT_TYPES)) { |
| default_type_mask |= INT_TYPES; |
| } |
| if (type_constraints.allows_any_of(FLOAT_TYPES)) { |
| default_type_mask |= FLOAT_TYPES; |
| } |
| |
| if (default_type_mask.none()) { |
| return {}; |
| } |
| |
| UnOpMask mask = cfg_.un_op_mask; |
| while (mask.any()) { |
| auto op = (UnOp)rng_->gen_un_op(mask); |
| |
| SpecificTypes expr_types; |
| switch (op) { |
| case UnOp::Plus: |
| case UnOp::Neg: |
| expr_types = default_type_mask; |
| break; |
| |
| case UnOp::BitNot: |
| expr_types = INT_TYPES; |
| break; |
| |
| case UnOp::LogicalNot: |
| expr_types = SpecificTypes::all_in_bool_ctx(); |
| break; |
| |
| default: |
| lldb_eval_unreachable( |
| "Unhandled switch case, did you introduce a new unary " |
| "operator?"); |
| } |
| |
| auto maybe_expr = |
| gen_with_weights(weights, TypeConstraints(std::move(expr_types))); |
| if (!maybe_expr.has_value()) { |
| mask[op] = false; |
| continue; |
| } |
| Expr expr = std::move(maybe_expr.value()); |
| |
| if (expr_precedence(expr) > UnaryExpr::PRECEDENCE) { |
| expr = ParenthesizedExpr(expr); |
| } |
| |
| return UnaryExpr(op, std::move(expr)); |
| } |
| |
| return {}; |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_ternary_expr( |
| const Weights& weights, const ExprConstraints& constraints) { |
| auto maybe_cond = gen_with_weights( |
| weights, TypeConstraints(SpecificTypes::all_in_bool_ctx())); |
| if (!maybe_cond.has_value()) { |
| return {}; |
| } |
| Expr cond = std::move(maybe_cond.value()); |
| |
| const auto& type_constraints = constraints.type_constraints(); |
| auto maybe_type = gen_type(weights, type_constraints); |
| if (!maybe_type.has_value()) { |
| return {}; |
| } |
| auto& type = maybe_type.value(); |
| |
| ExprConstraints new_constraints; |
| if (constraints.must_be_lvalue()) { |
| new_constraints = |
| ExprConstraints(SpecificTypes(type), ExprCategory::Lvalue); |
| } else if (std::holds_alternative<ScalarType>(type)) { |
| ScalarMask mask = INT_TYPES; |
| if (type_constraints.allows_any_of(FLOAT_TYPES)) { |
| mask |= FLOAT_TYPES; |
| } |
| |
| new_constraints = TypeConstraints(SpecificTypes(mask)); |
| } else { |
| new_constraints = TypeConstraints(SpecificTypes(type)); |
| } |
| |
| auto maybe_lhs = gen_with_weights(weights, new_constraints); |
| if (!maybe_lhs.has_value()) { |
| return {}; |
| } |
| Expr lhs = std::move(maybe_lhs.value()); |
| |
| auto maybe_rhs = gen_with_weights(weights, new_constraints); |
| if (!maybe_rhs.has_value()) { |
| return {}; |
| } |
| Expr rhs = std::move(maybe_rhs.value()); |
| |
| if (expr_precedence(cond) == TernaryExpr::PRECEDENCE) { |
| cond = ParenthesizedExpr(cond); |
| } |
| |
| return TernaryExpr(std::move(cond), std::move(lhs), std::move(rhs)); |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_cast_expr( |
| const Weights& weights, const ExprConstraints& constraints) { |
| if (constraints.must_be_lvalue()) { |
| return {}; |
| } |
| |
| auto maybe_type = gen_type(weights, constraints.type_constraints()); |
| if (!maybe_type.has_value()) { |
| return {}; |
| } |
| Type type = std::move(maybe_type.value()); |
| |
| SpecificTypes expr_types; |
| if (std::holds_alternative<TaggedType>(type)) { |
| return {}; |
| } else if (std::holds_alternative<PointerType>(type)) { |
| expr_types = SpecificTypes::make_any_pointer_constraints(); |
| } else { |
| expr_types = INT_TYPES | FLOAT_TYPES; |
| } |
| |
| auto maybe_expr = |
| gen_with_weights(weights, TypeConstraints(std::move(expr_types))); |
| if (!maybe_expr.has_value()) { |
| return {}; |
| } |
| Expr expr = std::move(maybe_expr.value()); |
| |
| if (expr_precedence(expr) > CastExpr::PRECEDENCE) { |
| expr = ParenthesizedExpr(std::move(expr)); |
| } |
| |
| return CastExpr(std::move(type), std::move(expr)); |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_address_of_expr( |
| const Weights&, const ExprConstraints& constraints) { |
| // TODO: Stub implemented in branch `fuzzer-ptrs` |
| if (constraints.must_be_lvalue()) { |
| return {}; |
| } |
| |
| return {}; |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_member_of_expr( |
| const Weights& weights, const ExprConstraints& constraints) { |
| auto maybe_expr = gen_with_weights(weights, constraints); |
| if (!maybe_expr.has_value()) { |
| return {}; |
| } |
| Expr expr = maybe_expr.value(); |
| |
| if (expr_precedence(expr) > MemberOf::PRECEDENCE) { |
| expr = ParenthesizedExpr(std::move(expr)); |
| } |
| |
| // TODO: Stub |
| return MemberOf(std::move(expr), "f1"); |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_member_of_ptr_expr( |
| const Weights& weights, const ExprConstraints& constraints) { |
| auto maybe_expr = gen_with_weights(weights, constraints); |
| if (!maybe_expr.has_value()) { |
| return {}; |
| } |
| Expr expr = std::move(maybe_expr.value()); |
| |
| if (expr_precedence(expr) > MemberOfPtr::PRECEDENCE) { |
| expr = ParenthesizedExpr(std::move(expr)); |
| } |
| |
| // TODO: Stub |
| return MemberOfPtr(std::move(expr), "f1"); |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_array_index_expr( |
| const Weights& weights, const ExprConstraints& constraints) { |
| TypeConstraints idx_constraints = SpecificTypes(INT_TYPES); |
| |
| auto maybe_expr = gen_with_weights(weights, constraints); |
| if (!maybe_expr.has_value()) { |
| return {}; |
| } |
| Expr expr = std::move(maybe_expr.value()); |
| |
| auto maybe_idx = gen_with_weights(weights, idx_constraints); |
| if (!maybe_idx.has_value()) { |
| return {}; |
| } |
| Expr idx = std::move(maybe_idx.value()); |
| |
| if (expr_precedence(expr) > ArrayIndex::PRECEDENCE) { |
| expr = ParenthesizedExpr(std::move(expr)); |
| } |
| |
| return ArrayIndex(std::move(expr), std::move(idx)); |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_dereference_expr( |
| const Weights& weights, const ExprConstraints& constraints) { |
| auto maybe_expr = gen_with_weights(weights, constraints); |
| if (!maybe_expr.has_value()) { |
| return {}; |
| } |
| Expr expr = std::move(maybe_expr.value()); |
| |
| if (expr_precedence(expr) > DereferenceExpr::PRECEDENCE) { |
| expr = ParenthesizedExpr(std::move(expr)); |
| } |
| |
| return DereferenceExpr(std::move(expr)); |
| } |
| |
| std::optional<Expr> ExprGenerator::gen_with_weights( |
| const Weights& weights, const ExprConstraints& constraints) { |
| Weights new_weights = weights; |
| |
| ExprKindMask mask = cfg_.expr_kind_mask; |
| while (mask.any()) { |
| auto kind = rng_->gen_expr_kind(new_weights, mask); |
| auto idx = (size_t)kind; |
| |
| auto old_weight = new_weights[kind]; |
| new_weights[kind] *= cfg_.expr_kind_weights[idx].dampening_factor; |
| |
| std::optional<Expr> maybe_expr; |
| switch (kind) { |
| case ExprKind::IntegerConstant: |
| maybe_expr = gen_integer_constant(constraints); |
| break; |
| |
| case ExprKind::DoubleConstant: |
| maybe_expr = gen_double_constant(constraints); |
| break; |
| |
| case ExprKind::VariableExpr: |
| maybe_expr = gen_variable_expr(constraints); |
| break; |
| |
| case ExprKind::BinaryExpr: |
| maybe_expr = gen_binary_expr(new_weights, constraints); |
| break; |
| |
| case ExprKind::UnaryExpr: |
| maybe_expr = gen_unary_expr(new_weights, constraints); |
| break; |
| |
| case ExprKind::TernaryExpr: |
| maybe_expr = gen_ternary_expr(new_weights, constraints); |
| break; |
| |
| case ExprKind::BooleanConstant: |
| maybe_expr = gen_boolean_constant(constraints); |
| break; |
| |
| case ExprKind::CastExpr: |
| maybe_expr = gen_cast_expr(new_weights, constraints); |
| break; |
| |
| case ExprKind::DereferenceExpr: |
| maybe_expr = gen_dereference_expr(new_weights, constraints); |
| break; |
| |
| case ExprKind::AddressOf: |
| maybe_expr = gen_address_of_expr(new_weights, constraints); |
| break; |
| |
| case ExprKind::MemberOf: |
| maybe_expr = gen_member_of_expr(new_weights, constraints); |
| break; |
| |
| case ExprKind::MemberOfPtr: |
| maybe_expr = gen_member_of_ptr_expr(new_weights, constraints); |
| break; |
| |
| case ExprKind::ArrayIndex: |
| maybe_expr = gen_array_index_expr(new_weights, constraints); |
| break; |
| |
| default: |
| lldb_eval_unreachable("Unhandled expression generation case"); |
| } |
| |
| if (!maybe_expr.has_value()) { |
| new_weights[kind] = old_weight; |
| mask[kind] = false; |
| |
| continue; |
| } |
| |
| return maybe_parenthesized(std::move(maybe_expr.value())); |
| } |
| |
| return {}; |
| } |
| |
| Expr ExprGenerator::maybe_parenthesized(Expr expr) { |
| if (rng_->gen_parenthesize(cfg_.parenthesize_prob)) { |
| return ParenthesizedExpr(std::move(expr)); |
| } |
| |
| return expr; |
| } |
| |
| std::optional<Type> ExprGenerator::gen_type( |
| const Weights& weights, const TypeConstraints& constraints) { |
| return gen_type_impl(weights, constraints); |
| } |
| |
| std::optional<Type> ExprGenerator::gen_type_impl( |
| const Weights& weights, const TypeConstraints& type_constraints) { |
| if (!type_constraints.satisfiable()) { |
| return {}; |
| } |
| |
| Weights new_weights = weights; |
| TypeKindMask mask = TypeKindMask::all_set(); |
| |
| if (type_constraints.allowed_scalar_types().none()) { |
| mask[TypeKind::ScalarType] = false; |
| } |
| if (!type_constraints.allows_tagged_types()) { |
| mask[TypeKind::TaggedType] = false; |
| } |
| if (!type_constraints.allows_pointer() && |
| !type_constraints.allows_void_pointer()) { |
| mask[TypeKind::TaggedType] = false; |
| } |
| |
| while (mask.any()) { |
| auto choice = rng_->gen_type_kind(new_weights, mask); |
| auto idx = (size_t)choice; |
| |
| auto& new_type_weights = new_weights.type_weights(); |
| auto old_weight = new_type_weights[idx]; |
| new_type_weights[idx] *= cfg_.type_kind_weights[idx].dampening_factor; |
| |
| std::optional<Type> maybe_type; |
| switch (choice) { |
| case TypeKind::ScalarType: |
| maybe_type = gen_scalar_type(type_constraints); |
| break; |
| |
| case TypeKind::TaggedType: |
| maybe_type = gen_tagged_type(type_constraints); |
| break; |
| |
| case TypeKind::PointerType: |
| maybe_type = gen_pointer_type(new_weights, type_constraints); |
| break; |
| |
| case TypeKind::VoidPointerType: |
| maybe_type = gen_void_pointer_type(type_constraints); |
| break; |
| } |
| |
| if (maybe_type.has_value()) { |
| return maybe_type; |
| } |
| |
| new_type_weights[idx] = old_weight; |
| mask[choice] = false; |
| } |
| |
| return {}; |
| } |
| |
| std::optional<QualifiedType> ExprGenerator::gen_qualified_type( |
| const Weights& weights, const TypeConstraints& constraints) { |
| auto maybe_type = gen_type_impl(weights, constraints); |
| if (!maybe_type.has_value()) { |
| return {}; |
| } |
| Type type = std::move(maybe_type.value()); |
| auto qualifiers = gen_cv_qualifiers(); |
| |
| return QualifiedType(std::move(type), qualifiers); |
| } |
| |
| std::optional<Type> ExprGenerator::gen_pointer_type( |
| const Weights& weights, const TypeConstraints& constraints) { |
| if (!constraints.allows_pointer() && !constraints.allows_void_pointer()) { |
| return {}; |
| } |
| |
| auto maybe_type = |
| gen_qualified_type(weights, constraints.allowed_to_point_to()); |
| if (!maybe_type.has_value()) { |
| return {}; |
| } |
| |
| return PointerType(std::move(maybe_type.value())); |
| } |
| |
| std::optional<Type> ExprGenerator::gen_void_pointer_type( |
| const TypeConstraints& constraints) { |
| if (!constraints.allows_void_pointer()) { |
| return {}; |
| } |
| |
| auto cv_qualifiers = |
| rng_->gen_cv_qualifiers(cfg_.const_prob, cfg_.volatile_prob); |
| |
| return PointerType(QualifiedType(ScalarType::Void, cv_qualifiers)); |
| } |
| |
| std::optional<Type> ExprGenerator::gen_tagged_type( |
| const TypeConstraints& constraints) { |
| if (!constraints.allows_tagged_types()) { |
| return {}; |
| } |
| |
| const auto* tagged_types = constraints.allowed_tagged_types(); |
| if (tagged_types == nullptr) { |
| return TaggedType("TestStruct"); |
| } |
| |
| auto it = tagged_types->begin(); |
| if (it != tagged_types->end()) { |
| return *it; |
| } |
| |
| return {}; |
| } |
| |
| std::optional<Type> ExprGenerator::gen_scalar_type( |
| const TypeConstraints& constraints) { |
| ScalarMask mask = constraints.allowed_scalar_types(); |
| if (mask.none()) { |
| return {}; |
| } |
| |
| return rng_->gen_scalar_type(mask); |
| } |
| |
| CvQualifiers ExprGenerator::gen_cv_qualifiers() { |
| return rng_->gen_cv_qualifiers(cfg_.const_prob, cfg_.volatile_prob); |
| } |
| |
| std::optional<Expr> ExprGenerator::generate() { |
| Weights weights; |
| |
| auto& expr_weights = weights.expr_weights(); |
| for (size_t i = 0; i < expr_weights.size(); i++) { |
| expr_weights[i] = cfg_.expr_kind_weights[i].initial_weight; |
| } |
| |
| auto& type_weights = weights.type_weights(); |
| for (size_t i = 0; i < type_weights.size(); i++) { |
| type_weights[i] = cfg_.type_kind_weights[i].initial_weight; |
| } |
| |
| return gen_with_weights(weights, |
| TypeConstraints(SpecificTypes::all_in_bool_ctx())); |
| } |
| |
| template <typename Enum, typename Rng> |
| Enum pick_nth_set_bit(const EnumBitset<Enum> mask, Rng& rng) { |
| // At least one bit needs to be set |
| assert(mask.any() && "Mask must not be empty"); |
| |
| std::uniform_int_distribution<size_t> distr(1, mask.count()); |
| size_t choice = distr(rng); |
| |
| size_t running_ones = 0; |
| for (size_t i = 0; i < mask.size(); i++) { |
| if (mask[i]) { |
| running_ones++; |
| } |
| |
| if (running_ones == choice) { |
| return (Enum)i; |
| } |
| } |
| |
| // `choice` lies in the range `[1, mask.count()]`, `running_ones` will |
| // always lie in the range `[0, mask.count()]` and is incremented at most |
| // once per loop iteration. The only way for this assertion to fire is for |
| // `mask` to be empty (which we have asserted beforehand). |
| lldb_eval_unreachable("Mask has no bits set"); |
| } |
| |
| template <typename Enum, typename Rng, typename RealType> |
| Enum weighted_pick( |
| const std::array<RealType, (size_t)Enum::EnumLast + 1>& array, |
| const EnumBitset<Enum>& mask, Rng& rng) { |
| static_assert(std::is_floating_point_v<RealType>, |
| "Must be a floating point type"); |
| |
| RealType sum = 0; |
| for (size_t i = 0; i < array.size(); i++) { |
| sum += mask[i] ? array[i] : 0; |
| } |
| |
| std::uniform_real_distribution<RealType> distr(0, sum); |
| RealType choice = distr(rng); |
| |
| RealType running_sum = 0; |
| for (size_t i = 0; i < array.size(); i++) { |
| running_sum += mask[i] ? array[i] : 0; |
| if (choice < running_sum) { |
| return (Enum)i; |
| } |
| } |
| |
| lldb_eval_unreachable("Could not pick an element; maybe sum is 0?"); |
| } |
| |
| BinOp DefaultGeneratorRng::gen_bin_op(BinOpMask mask) { |
| return pick_nth_set_bit(mask, rng_); |
| } |
| |
| UnOp DefaultGeneratorRng::gen_un_op(UnOpMask mask) { |
| return pick_nth_set_bit(mask, rng_); |
| } |
| |
| IntegerConstant DefaultGeneratorRng::gen_integer_constant(uint64_t min, |
| uint64_t max) { |
| using Base = IntegerConstant::Base; |
| using Length = IntegerConstant::Length; |
| using Signedness = IntegerConstant::Signedness; |
| |
| std::uniform_int_distribution<uint64_t> distr(min, max); |
| auto value = distr(rng_); |
| |
| std::uniform_int_distribution<int> base_distr((int)Base::EnumFirst, |
| (int)Base::EnumLast); |
| auto base = (Base)base_distr(rng_); |
| |
| std::uniform_int_distribution<int> length_distr((int)Length::EnumFirst, |
| (int)Length::EnumLast); |
| auto length = (Length)base_distr(rng_); |
| |
| std::uniform_int_distribution<int> sign_distr((int)Signedness::EnumFirst, |
| (int)Signedness::EnumLast); |
| auto signedness = (Signedness)base_distr(rng_); |
| |
| return IntegerConstant(value, base, length, signedness); |
| } |
| |
| DoubleConstant DefaultGeneratorRng::gen_double_constant(double min, |
| double max) { |
| using Format = DoubleConstant::Format; |
| using Length = DoubleConstant::Length; |
| |
| std::uniform_real_distribution<double> distr(min, max); |
| auto value = distr(rng_); |
| |
| std::uniform_int_distribution<int> format_distr((int)Format::EnumFirst, |
| (int)Format::EnumLast); |
| auto format = (Format)format_distr(rng_); |
| |
| std::uniform_int_distribution<int> length_distr((int)Length::EnumFirst, |
| (int)Length::EnumLast); |
| auto length = (Length)length_distr(rng_); |
| |
| return DoubleConstant(value, format, length); |
| } |
| |
| CvQualifiers DefaultGeneratorRng::gen_cv_qualifiers(float const_prob, |
| float volatile_prob) { |
| std::bernoulli_distribution const_distr(const_prob); |
| std::bernoulli_distribution volatile_distr(volatile_prob); |
| |
| CvQualifiers retval; |
| retval[CvQualifier::Const] = const_distr(rng_); |
| retval[CvQualifier::Volatile] = volatile_distr(rng_); |
| |
| return retval; |
| } |
| |
| bool DefaultGeneratorRng::gen_binop_ptr_expr(float probability) { |
| std::bernoulli_distribution distr(probability); |
| return distr(rng_); |
| } |
| |
| bool DefaultGeneratorRng::gen_binop_flip_operands(float probability) { |
| std::bernoulli_distribution distr(probability); |
| return distr(rng_); |
| } |
| |
| bool DefaultGeneratorRng::gen_binop_ptrdiff_expr(float probability) { |
| std::bernoulli_distribution distr(probability); |
| return distr(rng_); |
| } |
| |
| bool DefaultGeneratorRng::gen_parenthesize(float probability) { |
| std::bernoulli_distribution distr(probability); |
| return distr(rng_); |
| } |
| |
| bool DefaultGeneratorRng::gen_boolean() { |
| std::bernoulli_distribution distr; |
| return distr(rng_); |
| } |
| |
| ExprKind DefaultGeneratorRng::gen_expr_kind(const Weights& weights, |
| const ExprKindMask& mask) { |
| return weighted_pick(weights.expr_weights(), mask, rng_); |
| } |
| |
| TypeKind DefaultGeneratorRng::gen_type_kind(const Weights& weights, |
| const TypeKindMask& mask) { |
| return weighted_pick(weights.type_weights(), mask, rng_); |
| } |
| |
| ScalarType DefaultGeneratorRng::gen_scalar_type(ScalarMask mask) { |
| return pick_nth_set_bit(mask, rng_); |
| } |
| |
| } // namespace fuzzer |