├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── Oracle.py ├── README.md ├── main ├── CMakeLists.txt └── main.cpp └── mutator ├── CMakeLists.txt ├── include └── LookUB │ └── mutator │ ├── Canonicalizer.h │ ├── CodeMoving.h │ ├── FunctionMutator.h │ ├── LiteralMaker.h │ ├── Simplifier.h │ ├── Snippets.h │ ├── StatementContext.h │ ├── StatementCreator.h │ ├── StatementMutator.h │ ├── Type.def │ ├── TypeCreator.h │ ├── UnsafeDecisions.def │ ├── UnsafeGenerator.h │ ├── UnsafeMutatorBase.h │ └── UnsafeStrategy.h ├── src ├── Canonicalizer.cpp ├── CodeMoving.cpp ├── FunctionMutator.cpp ├── LiteralMaker.cpp ├── Simplifier.cpp ├── Snippets.cpp ├── StatementContext.cpp ├── StatementCreator.cpp ├── StatementMutator.cpp ├── TypeCreator.cpp ├── UnsafeGenerator.cpp ├── UnsafeMutatorBase.cpp └── UnsafeStrategy.cpp └── test ├── Canonicalizer.test.cpp ├── CodeMoving.test.cpp ├── FunctionMutator.test.cpp ├── LiteralMaker.test.cpp ├── Simplifier.test.cpp ├── Snippets.test.cpp ├── StatementContext.test.cpp ├── StatementCreator.test.cpp ├── StatementMutator.test.cpp ├── TypeCreator.test.cpp ├── UnsafeGenerator.test.cpp ├── UnsafeMutatorBase.test.cpp └── UnsafeStrategy.test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | # BasedOnStyle: LLVM 4 | AccessModifierOffset: -2 5 | AlignAfterOpenBracket: Align 6 | AlignArrayOfStructures: None 7 | AlignConsecutiveAssignments: 8 | Enabled: false 9 | AcrossEmptyLines: false 10 | AcrossComments: false 11 | AlignCompound: false 12 | PadOperators: true 13 | AlignConsecutiveBitFields: 14 | Enabled: false 15 | AcrossEmptyLines: false 16 | AcrossComments: false 17 | AlignCompound: false 18 | PadOperators: false 19 | AlignConsecutiveDeclarations: 20 | Enabled: false 21 | AcrossEmptyLines: false 22 | AcrossComments: false 23 | AlignCompound: false 24 | PadOperators: false 25 | AlignConsecutiveMacros: 26 | Enabled: false 27 | AcrossEmptyLines: false 28 | AcrossComments: false 29 | AlignCompound: false 30 | PadOperators: false 31 | AlignConsecutiveShortCaseStatements: 32 | Enabled: false 33 | AcrossEmptyLines: false 34 | AcrossComments: false 35 | AlignCaseColons: false 36 | AlignEscapedNewlines: Right 37 | AlignOperands: Align 38 | AlignTrailingComments: 39 | Kind: Always 40 | OverEmptyLines: 0 41 | AllowAllArgumentsOnNextLine: true 42 | AllowAllParametersOfDeclarationOnNextLine: true 43 | AllowShortBlocksOnASingleLine: Never 44 | AllowShortCaseLabelsOnASingleLine: false 45 | AllowShortEnumsOnASingleLine: true 46 | AllowShortFunctionsOnASingleLine: All 47 | AllowShortIfStatementsOnASingleLine: Never 48 | AllowShortLambdasOnASingleLine: All 49 | AllowShortLoopsOnASingleLine: false 50 | AlwaysBreakAfterDefinitionReturnType: None 51 | AlwaysBreakAfterReturnType: None 52 | AlwaysBreakBeforeMultilineStrings: false 53 | AlwaysBreakTemplateDeclarations: MultiLine 54 | AttributeMacros: 55 | - __capability 56 | BinPackArguments: true 57 | BinPackParameters: true 58 | BitFieldColonSpacing: Both 59 | BraceWrapping: 60 | AfterCaseLabel: false 61 | AfterClass: false 62 | AfterControlStatement: Never 63 | AfterEnum: false 64 | AfterExternBlock: false 65 | AfterFunction: false 66 | AfterNamespace: false 67 | AfterObjCDeclaration: false 68 | AfterStruct: false 69 | AfterUnion: false 70 | BeforeCatch: false 71 | BeforeElse: false 72 | BeforeLambdaBody: false 73 | BeforeWhile: false 74 | IndentBraces: false 75 | SplitEmptyFunction: true 76 | SplitEmptyRecord: true 77 | SplitEmptyNamespace: true 78 | BreakAfterAttributes: Never 79 | BreakAfterJavaFieldAnnotations: false 80 | BreakArrays: true 81 | BreakBeforeBinaryOperators: None 82 | BreakBeforeConceptDeclarations: Always 83 | BreakBeforeBraces: Attach 84 | BreakBeforeInlineASMColon: OnlyMultiline 85 | BreakBeforeTernaryOperators: true 86 | BreakConstructorInitializers: BeforeColon 87 | BreakInheritanceList: BeforeColon 88 | BreakStringLiterals: true 89 | ColumnLimit: 80 90 | CommentPragmas: '^ IWYU pragma:' 91 | CompactNamespaces: false 92 | ConstructorInitializerIndentWidth: 4 93 | ContinuationIndentWidth: 4 94 | Cpp11BracedListStyle: true 95 | DerivePointerAlignment: false 96 | DisableFormat: false 97 | EmptyLineAfterAccessModifier: Never 98 | EmptyLineBeforeAccessModifier: LogicalBlock 99 | ExperimentalAutoDetectBinPacking: false 100 | FixNamespaceComments: true 101 | ForEachMacros: 102 | - foreach 103 | - Q_FOREACH 104 | - BOOST_FOREACH 105 | IfMacros: 106 | - KJ_IF_MAYBE 107 | IncludeBlocks: Preserve 108 | IncludeCategories: 109 | - Regex: '^"(llvm|llvm-c|clang|clang-c)/' 110 | Priority: 2 111 | SortPriority: 0 112 | CaseSensitive: false 113 | - Regex: '^(<|"(gtest|gmock|isl|json)/)' 114 | Priority: 3 115 | SortPriority: 0 116 | CaseSensitive: false 117 | - Regex: '.*' 118 | Priority: 1 119 | SortPriority: 0 120 | CaseSensitive: false 121 | IncludeIsMainRegex: '(Test)?$' 122 | IncludeIsMainSourceRegex: '' 123 | IndentAccessModifiers: false 124 | IndentCaseBlocks: false 125 | IndentCaseLabels: false 126 | IndentExternBlock: AfterExternBlock 127 | IndentGotoLabels: true 128 | IndentPPDirectives: None 129 | IndentRequiresClause: true 130 | IndentWidth: 2 131 | IndentWrappedFunctionNames: false 132 | InsertBraces: false 133 | InsertNewlineAtEOF: false 134 | InsertTrailingCommas: None 135 | IntegerLiteralSeparator: 136 | Binary: 0 137 | BinaryMinDigits: 0 138 | Decimal: 0 139 | DecimalMinDigits: 0 140 | Hex: 0 141 | HexMinDigits: 0 142 | JavaScriptQuotes: Leave 143 | JavaScriptWrapImports: true 144 | KeepEmptyLinesAtTheStartOfBlocks: true 145 | KeepEmptyLinesAtEOF: false 146 | LambdaBodyIndentation: Signature 147 | LineEnding: DeriveLF 148 | MacroBlockBegin: '' 149 | MacroBlockEnd: '' 150 | MaxEmptyLinesToKeep: 1 151 | NamespaceIndentation: None 152 | ObjCBinPackProtocolList: Auto 153 | ObjCBlockIndentWidth: 2 154 | ObjCBreakBeforeNestedBlockParam: true 155 | ObjCSpaceAfterProperty: false 156 | ObjCSpaceBeforeProtocolList: true 157 | PackConstructorInitializers: BinPack 158 | PenaltyBreakAssignment: 2 159 | PenaltyBreakBeforeFirstCallParameter: 19 160 | PenaltyBreakComment: 300 161 | PenaltyBreakFirstLessLess: 120 162 | PenaltyBreakOpenParenthesis: 0 163 | PenaltyBreakString: 1000 164 | PenaltyBreakTemplateDeclaration: 10 165 | PenaltyExcessCharacter: 1000000 166 | PenaltyIndentedWhitespace: 0 167 | PenaltyReturnTypeOnItsOwnLine: 60 168 | PointerAlignment: Right 169 | PPIndentWidth: -1 170 | QualifierAlignment: Leave 171 | ReferenceAlignment: Pointer 172 | ReflowComments: true 173 | RemoveBracesLLVM: false 174 | RemoveParentheses: Leave 175 | RemoveSemicolon: false 176 | RequiresClausePosition: OwnLine 177 | RequiresExpressionIndentation: OuterScope 178 | SeparateDefinitionBlocks: Leave 179 | ShortNamespaceLines: 1 180 | SortIncludes: CaseSensitive 181 | SortJavaStaticImport: Before 182 | SortUsingDeclarations: LexicographicNumeric 183 | SpaceAfterCStyleCast: false 184 | SpaceAfterLogicalNot: false 185 | SpaceAfterTemplateKeyword: true 186 | SpaceAroundPointerQualifiers: Default 187 | SpaceBeforeAssignmentOperators: true 188 | SpaceBeforeCaseColon: false 189 | SpaceBeforeCpp11BracedList: false 190 | SpaceBeforeCtorInitializerColon: true 191 | SpaceBeforeInheritanceColon: true 192 | SpaceBeforeJsonColon: false 193 | SpaceBeforeParens: ControlStatements 194 | SpaceBeforeParensOptions: 195 | AfterControlStatements: true 196 | AfterForeachMacros: true 197 | AfterFunctionDefinitionName: false 198 | AfterFunctionDeclarationName: false 199 | AfterIfMacros: true 200 | AfterOverloadedOperator: false 201 | AfterRequiresInClause: false 202 | AfterRequiresInExpression: false 203 | BeforeNonEmptyParentheses: false 204 | SpaceBeforeRangeBasedForLoopColon: true 205 | SpaceBeforeSquareBrackets: false 206 | SpaceInEmptyBlock: false 207 | SpacesBeforeTrailingComments: 1 208 | SpacesInAngles: Never 209 | SpacesInContainerLiterals: true 210 | SpacesInLineCommentPrefix: 211 | Minimum: 1 212 | Maximum: -1 213 | SpacesInParens: Never 214 | SpacesInParensOptions: 215 | InCStyleCasts: false 216 | InConditionalStatements: false 217 | InEmptyParentheses: false 218 | Other: false 219 | SpacesInSquareBrackets: false 220 | Standard: Latest 221 | StatementAttributeLikeMacros: 222 | - Q_EMIT 223 | StatementMacros: 224 | - Q_UNUSED 225 | - QT_REQUIRE_VERSION 226 | TabWidth: 8 227 | UseTab: Never 228 | VerilogBreakBetweenInstancePorts: true 229 | WhitespaceSensitiveMacros: 230 | - BOOST_PP_STRINGIZE 231 | - CF_SWIFT_NAME 232 | - NS_SWIFT_NAME 233 | - PP_STRINGIZE 234 | - STRINGIZE 235 | ... 236 | 237 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dbg 2 | rel 3 | *.user 4 | *.o 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "scc"] 2 | path = scc 3 | url = git@github.com:vusec/scc 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.11) 2 | project(LookUB) 3 | 4 | set(CMAKE_CXX_STANDARD 17) 5 | 6 | enable_testing() 7 | set(TARGET_RUNTIME_DIR "${CMAKE_BINARY_DIR}/runtime") 8 | add_subdirectory(scc) 9 | include(GoogleTest) 10 | 11 | set(FUZZ_PROJECT_NAME LookUB) 12 | add_subdirectory(mutator) 13 | add_subdirectory(main) 14 | -------------------------------------------------------------------------------- /Oracle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import os 5 | import subprocess as sp 6 | import argparse 7 | from oracle_utils import * 8 | 9 | parser = argparse.ArgumentParser(description='') 10 | parser.add_argument('--opt', dest='opt', action='store', default="2") 11 | parser.add_argument('--search', dest='needle', action='store', default=None) 12 | parser.add_argument('--sanitizer', dest='sanitizer', action='store', default="address") 13 | parser.add_argument('--fitness', dest='fitness', action='store_true', default=False) 14 | args = parser.parse_args(sys.argv[2:-1]) 15 | 16 | # The optimization level to use. 17 | opt_level = "-O" + args.opt 18 | # The search needle to look for on O0 (or None) 19 | needle = args.needle 20 | sanitizer = args.sanitizer 21 | # Whether to use the fitness score. 22 | use_scoring = args.fitness or not (needle is None) 23 | 24 | compiler = sys.argv[1] 25 | source_file = sys.argv[-1] 26 | 27 | def score(msg, score): 28 | actual_score = score if use_scoring else 0 29 | giveScore(msg, actual_score) 30 | 31 | 32 | # Ignore programs that are too large. The fuzzer rarely makes programs 33 | # that are this large, but if they are then avoid that we take down 34 | # the host system by consuming too much memory. 35 | if os.path.getsize(source_file) > 10000: 36 | score("too large source", -30000000) 37 | 38 | # A set of flags passed to all instances. 39 | base_flags = ["-g", "-w"] 40 | 41 | is_clang = ("clang++" in compiler) 42 | is_gcc = not is_clang 43 | 44 | # The list of sanitizers we want to test. 45 | # TODO: You can shift the order around to avoid and might get different 46 | # results. Note that you can't just shuffle around this list via e.g. 47 | # Python's random module as otherwise the non-deterministic oracle 48 | # confuses the fuzzer. 49 | sanitizers = ["address", "undefined"] 50 | if is_clang: 51 | sanitizers += ["memory"] 52 | 53 | if not sanitizer in sanitizers: 54 | score("Unknown sanitizer: " + sanitizer, -1000) 55 | sanitizers.remove(sanitizer) 56 | sanitizers = [sanitizer] + sanitizers 57 | 58 | # The timeout we use for compiling/running. 59 | timeout = 1 60 | 61 | # Keep track if we encountered a sanitizer failure on O0. 62 | had_error = False 63 | 64 | # If the user set ASAN_OPTIONS to disable features, re-enable them to 65 | # make sure we don't miss bugs. 66 | os.environ["ASAN_OPTIONS"] = "detect_leaks=1,detect_stack_use_after_return=1" 67 | 68 | # Additional string that is sent back to the fuzzer (where it is displayed 69 | # to the user). 70 | extra_info = "" 71 | 72 | # Returns true if the given stderr indicates an actual sanitizer failure. 73 | # This is necessary as sanitizers hook into some handlers (e.g., SIGSEGV) 74 | # and pretend they found some kind of UB. But they actually just caught 75 | # some random unrelated crash by accident. 76 | def isSanitizerError(stderr): 77 | # Stack overflows just disappear on optimization and are always 78 | # false positives. 79 | if ': stack-overflow ' in stderr: 80 | score(prefix + "Ignoring stack-verflow: " + stderr, -80) 81 | 82 | # We hit an error where the sanitizer complains an allocation is too large. 83 | if 'maximum supported size' in stderr: 84 | score(prefix + "Ignoring too large allocation err: " + stderr, -80) 85 | 86 | # GCC's UBSan doesn't print anything on segfaults. 87 | if is_gcc and len(stderr) == 0: 88 | return False 89 | 90 | # Internal GCC error for invalid addresses. Not really a sanitizer check. 91 | if 'asan/asan_descriptions.cpp' in stderr: 92 | return False 93 | 94 | # Sanitizers intercepted a random crash and pretend they detected an issue. 95 | # Ignore this as this is just a false-positive. 96 | if 'unknown-crash on address' in stderr: 97 | return False 98 | 99 | # Same as above. 100 | if 'SEGV on unknown address' in stderr: 101 | return False 102 | 103 | # Same as above. 104 | if 'DEADLYSIGNAL' in stderr: 105 | return False 106 | return True 107 | 108 | # For GCC, we first have to find out if there is an uninitialized use. 109 | # GCC has no memory sanitizer, so an uninitialized use renders the program 110 | # useless for our testing purposes. 111 | if is_gcc: 112 | binary = compile(compiler, source_file, base_flags) 113 | 114 | try: 115 | res = sp.run(["valgrind", "--error-exitcode=1", binary], 116 | check=True, timeout=5, capture_output=True) 117 | except sp.TimeoutExpired as e: 118 | score("Timed out under valgrind", -80) 119 | except sp.CalledProcessError as e: 120 | if "uninitialised" in e.stderr.decode("utf-8"): 121 | # GCC has no MSan, so skip if we find an uninitialized use. 122 | score("Program depends on uninitialized value.", -80) 123 | # Otherwise we can search for sanitizer-eliding optimizations. 124 | pass 125 | 126 | # Try compiling with every supported sanitizer and see if we can optimize 127 | # away a sanitizer error. Note that we can't just enable all of them 128 | # at once as this just not compiles at all or causes bogus issues. 129 | for sanitizer in sanitizers: 130 | print("Testing sanitizer " + sanitizer) 131 | 132 | flags = base_flags + ["-fsanitize=" + sanitizer] 133 | 134 | sys.stdout.write(" -O0: ") 135 | prefix = "[" + sanitizer + "] " 136 | # First try without optimization. 137 | try: 138 | # First make sure the program has an sanitizer on O0. 139 | compileAndRun(compiler, source_file, flags) 140 | print(" No error on -O0") 141 | except FailedToCompile as e: 142 | # Just ignore programs if they somehow fail to compile. 143 | score(prefix + "Test program failed to compile: " + e.stderr.decode("utf-8"), -80) 144 | except TimeOutRunning as e: 145 | # If we timed out compiling then ignore the program. 146 | score(prefix + "Test program timed out", -80) 147 | except FailedToRun as e: 148 | stderr = e.stderr.decode("utf-8") 149 | # If we have a needle to look for on O0, check first and then abort if 150 | # it's not there. 151 | if needle and not (needle in stderr): 152 | score("Can't find search string in output", -1) 153 | # On error, filter out false positives. 154 | if isSanitizerError(e.stderr.decode("utf-8")): 155 | had_error = True 156 | print(" Detected error") 157 | else: 158 | print(" No error") 159 | 160 | # Try running with optimizations. 161 | sys.stdout.write(" " + opt_level + ": ") 162 | try: 163 | compileAndRun(compiler, source_file, flags + [opt_level]) 164 | print(" No error on " + opt_level) 165 | except FailedToCompile as e: 166 | # This really should never happen, but e.g., ICE's can cause this. 167 | score(prefix + "Optimized program failed to compile???", -80) 168 | except TimeOutRunning as e: 169 | # Ignore timeouts which are usually non-deterministic. 170 | score(prefix + "Failed to compile optimized program", -80) 171 | except FailedToRun as e: 172 | internal_crash = False 173 | # There is an optional check in libc that reports double free's. 174 | # This only happens when the sanitizer failed to detect the 175 | # double free itself. 176 | if "free(): double free detected in tcache 2" in e.stderr.decode("utf-8"): 177 | internal_crash = True 178 | extra_info = "(Bypassed sanitizer and crashed in libc)" 179 | 180 | # If we still find the issue after optimizations we didn't find an SEO. 181 | # Abort to save time. This can't be an SEO. 182 | if not internal_crash: 183 | print("stderr:" + e.stderr.decode("utf-8")) 184 | score(prefix + "Failure still found after optimization", -80) 185 | 186 | # If we didn't find any errors on O0 then we failed to make a buggy program. 187 | if not had_error: 188 | score("Program had no sanitizer error on O0", 0) 189 | 190 | # We had an error on O0 and no iteration of the loop above never aborted 191 | # this script, so there is no error on any O2 binary. 192 | markInteresting("Error is gone " + extra_info) 193 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LookUB 2 | 3 | LookUB is an automatic detection system for optimizations that elide sanitizer 4 | checks. 5 | 6 | ## Quick start 7 | 8 | ```bash 9 | # Clone repository. 10 | $ git clone https://github.com//LookUB 11 | # Create a build directory. 12 | $ mkdir -p LookUB/build ; cd LookUB/build 13 | # Build 14 | $ cmake .. && make 15 | # Search for eliding optimizations. 16 | $ ./bin/LookUB -- ../Oracle.py /usr/bin/clang++ 17 | ``` 18 | 19 | The generated test cases demonstrating sanitizer-eliding optimizations (SEO) 20 | will be stored in the `saved_testcases` directory. 21 | 22 | ## Command line arguments 23 | 24 | ```bash 25 | LookUB [MutatorArguments] -- ./Oracle.py /usr/bin/clang++ [OracleArguments] 26 | ``` 27 | 28 | ### Mutator arguments. 29 | 30 | These go before the `--` in the command line above. 31 | 32 | * `--simple-ui`: Disables the interactive UI. 33 | * `--stop-after-hit`: Stop fuzzing after finding a SEO (default: disabled). 34 | * `--stop-after=N`: Stop fuzzing after `N` SEOs (default: never). 35 | * `--scale=N`: How many mutations to apply each iteration (default: 1). 36 | 37 | * `--tries=N`: How many tries to give each program. 38 | * `--queue-size=N`: How many programs to keep in the queue. 39 | * `--step`: Manual stepping mode. Press space to generate and run a program. 40 | * `--reducer-tries=N`: How many tries to reduce programs. 41 | * `--ui-update=N`: UI update frequency (in ms) 42 | * `--splash`: Whether to show a startup splash. 43 | 44 | ### Oracle arguments. 45 | 46 | * `--opt=N`: Changes the optimization level to `N`. (default: `2` for `-O2`). 47 | * `--fitness`: Whether to use the fitness function (default: disabled). 48 | * `--search=STRING`: The search string to look for on O0. This is useful if you 49 | want to look for a specific sanitizer error. (default: None) 50 | * `--sanitizer=STRING`: The sanitizer to run first and search the `--search` 51 | string with. One of `address`, `undefined` or `memory`. (default: `address`) -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(${FUZZ_PROJECT_NAME} main.cpp ) 2 | target_link_libraries(${FUZZ_PROJECT_NAME} PUBLIC 3 | scc-driver 4 | LookUB-mutator 5 | ) 6 | 7 | add_dependencies(${FUZZ_PROJECT_NAME} scc-driver) 8 | set_target_properties(${FUZZ_PROJECT_NAME} 9 | PROPERTIES 10 | ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" 11 | LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib" 12 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" 13 | ) 14 | -------------------------------------------------------------------------------- /main/main.cpp: -------------------------------------------------------------------------------- 1 | #include "LookUB/mutator/UnsafeGenerator.h" 2 | #include "scc/driver/ArgParser.h" 3 | #include "scc/driver/Driver.h" 4 | #include "scc/driver/DriverUtils.h" 5 | #include "scc/mutator-utils/Scheduler.h" 6 | 7 | #include 8 | #include 9 | 10 | static void printUsage(std::string progamName) { 11 | std::cerr << "Usage: " << progamName 12 | << " [options...] -- oracle-bin oracle-arg1 oracle-arg2 ...\n"; 13 | std::cerr << "Options:\n"; 14 | std::cerr << " --lang-opts=PATH\n"; 15 | std::cerr << " --tries=N How many tries to give each program.\n"; 16 | std::cerr << " --queue-size=N How many programs to keep in the queue.\n"; 17 | std::cerr 18 | << " --scale=N How many mutations to apply each iteration.\n"; 19 | std::cerr << " --step Manual stepping mode.\n"; 20 | std::cerr << " --stop-after-hit Stop fuzzing after first finding.\n"; 21 | std::cerr << " --stop-after=N Stop fuzzing after N findings. \n"; 22 | std::cerr << " --reducer-tries=N How many tries to reduce programs. \n"; 23 | std::cerr << " --ui-update=N UI update frequency (in ms)\n"; 24 | std::cerr << " --splash Whether to show a startup splash.\n"; 25 | } 26 | 27 | template int generatorMain(const ArgParser &args) { 28 | LangOpts opts; 29 | if (!args.optsFile.empty()) 30 | if (auto error = opts.loadFromFile(args.optsFile)) { 31 | std::cerr << "Failed to parse option file: " + error->getMessage(); 32 | return 1; 33 | } 34 | 35 | // Create a scheduler and pass all the parsed command line args. 36 | Scheduler sched(args.seed, opts); 37 | std::cout << "Running with seed " << args.seed << "\n"; 38 | sched.setMaxQueueSize(args.queueSize); 39 | sched.setMaxRunLimit(args.tries); 40 | sched.setMutatorScale(args.mutatorScale); 41 | sched.setStopAfter(args.stopAfter); 42 | sched.setStopAfterHit(args.stopAfterHits); 43 | sched.setReducerTries(args.reducerTries); 44 | 45 | // Let the scheduler/generator handle unknown args. 46 | if (auto err = sched.handleArgs(args.unknownArgs)) { 47 | printUsage(args.argv0); 48 | std::cerr << err->getMessage() << "\n"; 49 | return 1; 50 | } 51 | 52 | // Create the command to run on every generated program. 53 | std::string evalCommand = args.getEvalCommand(); 54 | evalCommand = 55 | Gen::expandEvalCommand(args.argv0, evalCommand, RngSource(args.seed)); 56 | 57 | std::string saveDir = args.saveDir; 58 | if (!std::filesystem::create_directory(saveDir) && 59 | !std::filesystem::exists(saveDir)) { 60 | std::cerr << "Failed to create output dir '" << saveDir << "'.\n"; 61 | return 1; 62 | } 63 | 64 | // Create the driver that runs the scheduler and displays the UI. 65 | Driver driver( 66 | sched, evalCommand, [&sched]() { sched.step(); }, saveDir); 67 | driver.setSimpleUI(args.simpleUI); 68 | driver.setUpdateInterval(args.uiUpdateMs); 69 | driver.setManualStepping(args.manualStepping); 70 | driver.setPrefixFunc( 71 | [](const Program &p) { return Gen::getProgramPrefix(p); }); 72 | driver.setSuffixFunc( 73 | [](const Program &p) { return Gen::getProgramSuffix(p); }); 74 | 75 | // Run until we are done. 76 | driver.run(args.splash); 77 | return 0; 78 | } 79 | 80 | int main(int argc, char **argv) { 81 | if (argc < 2) { 82 | printUsage(argv[0]); 83 | return 1; 84 | } 85 | 86 | for (int arg = 0; arg < argc; ++arg) { 87 | std::string argValue = argv[arg]; 88 | if (argValue == "--") 89 | break; 90 | if (argValue != "--help") 91 | continue; 92 | printUsage(argv[0]); 93 | return 0; 94 | } 95 | 96 | DriverUtils::prependPythonPath(argv[0]); 97 | 98 | std::random_device d; 99 | ArgParser args; 100 | args.reducerTries = 0; 101 | // Use a simple UI if the output is not an interactive TTY. 102 | args.simpleUI = !DriverUtils::isStdoutAProperTerminal(); 103 | args.seed = d(); 104 | if (auto err = args.parse(argc, argv)) { 105 | std::cerr << "Failed to parse arguments: " << *err << "\n"; 106 | printUsage(args.argv0); 107 | return 1; 108 | } 109 | 110 | return generatorMain(args); 111 | } 112 | -------------------------------------------------------------------------------- /mutator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_module(mutator 2 | COMPONENTS 3 | Canonicalizer 4 | CodeMoving 5 | FunctionMutator 6 | LiteralMaker 7 | Simplifier 8 | Snippets 9 | StatementContext 10 | StatementCreator 11 | StatementMutator 12 | TypeCreator 13 | UnsafeGenerator 14 | UnsafeMutatorBase 15 | UnsafeStrategy 16 | DEPENDENCIES 17 | scc-mutator-utils 18 | ) 19 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/Canonicalizer.h: -------------------------------------------------------------------------------- 1 | #ifndef CANONICALIZER_H 2 | #define CANONICALIZER_H 3 | 4 | #include "scc/program/Statement.h" 5 | 6 | #include 7 | 8 | /// Transforms code into its 'canonical' form. This removes redundant parts of 9 | /// code that don't add any meaning. E.g. `{{code}}` becomes `{code}` as the 10 | /// extra compound statement doesn't add anything useful. 11 | class Canonicalizer { 12 | static std::optional canonicalize(const Statement &s); 13 | 14 | public: 15 | /// Tries to simplify the given code without changing any semantics. 16 | /// Returns none if the code can't be simplified. 17 | static std::optional canonicalizeStmt(const Statement &s) { 18 | auto res = canonicalize(s); 19 | // TODO: Add some sanity checks here? 20 | return res; 21 | } 22 | }; 23 | 24 | #endif // CANONICALIZER_H 25 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/CodeMoving.h: -------------------------------------------------------------------------------- 1 | #ifndef CODEMOVING_H 2 | #define CODEMOVING_H 3 | 4 | #include "UnsafeMutatorBase.h" 5 | 6 | /// Mutations that move code around the program. 7 | /// 8 | /// E.g. outlining and inlining of functions. 9 | class CodeMoving : public UnsafeMutatorBase { 10 | public: 11 | CodeMoving(UnsafeMutatorBase::MutatorData &input) 12 | : UnsafeMutatorBase(input) {} 13 | // TODO: unused. 14 | Modified outlineStatement(const StatementContext &ctx, Statement &s); 15 | // TODO: unused. 16 | Modified inlineCall(const StatementContext &ctx, Statement &call); 17 | }; 18 | 19 | #endif // CODEMOVING_H 20 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/FunctionMutator.h: -------------------------------------------------------------------------------- 1 | #ifndef FUNCTIONMUTATOR_H 2 | #define FUNCTIONMUTATOR_H 3 | 4 | #include "UnsafeMutatorBase.h" 5 | #include 6 | 7 | /// Mutates all data associated with a function except their bodies. 8 | class FunctionMutator : public UnsafeMutatorBase { 9 | std::string getRandomCallingConvName() { 10 | // Random locations that Clang/GCC claim to support. 11 | return std::string(getRng().pickOne( 12 | {"stdcall", "regcall", "pascal", "ms_abi", "sysv_abi", "vectorcall"})); 13 | } 14 | 15 | /// Return a random calling convention. 16 | std::string getRandomCallingConv() { 17 | return "__attribute__((" + getRandomCallingConvName() + "))"; 18 | } 19 | 20 | /// Returns a random unsigned integer string. 21 | std::string uintStr(unsigned limit) { 22 | return std::to_string(getRng().getBelow(limit)); 23 | } 24 | 25 | /// Returns a random function attribute. 26 | std::string getRandomFuncAttr() { 27 | // Dump of random Clang attributes. 28 | return getRng().pickOneVec( 29 | {"__attribute__((alloc_size(" + uintStr(4) + ")))", 30 | "__attribute__((alloc_size(" + uintStr(4) + ", " + uintStr(4) + ")))", 31 | "__attribute__((always_inline))", 32 | "__attribute__((assume_aligned (" + uintStr(4) + ")))", 33 | "__attribute__((const))", "__attribute__((disable_tail_calls))", 34 | "__attribute__((flatten))", "__attribute__((malloc))", 35 | "__attribute__((no_builtin))", "__attribute__((noinline))", 36 | "__attribute__((pure))", 37 | "__attribute__((no_caller_saved_registers, " + 38 | getRandomCallingConvName() + "))"}); 39 | } 40 | 41 | public: 42 | FunctionMutator(MutatorData &input) : UnsafeMutatorBase(input) {} 43 | 44 | /// Randomizes the function attributes of the given function. 45 | Modified randomizeFuncAttrs(Function &f) { 46 | if (decision(Frag::UseNonStdCallingConv)) { 47 | f.setCallingConv(getRandomCallingConv()); 48 | return Modified::Yes; 49 | } 50 | 51 | if (decision(Frag::UseFunctionAttr)) { 52 | f.addAttr(getRandomFuncAttr()); 53 | return Modified::Yes; 54 | } 55 | 56 | if (decision(Frag::UseSecondFunctionAttr)) { 57 | f.addAttr(getRandomFuncAttr()); 58 | return Modified::Yes; 59 | } 60 | 61 | if (decision(Frag::DeleteFuncAttrs)) { 62 | auto attrs = f.getAllAttrs(); 63 | if (attrs.empty()) 64 | return Modified::No; 65 | f.removeAttr(getRng().pickOneVec(attrs)); 66 | return Modified::Yes; 67 | } 68 | 69 | if (f.setWeight( 70 | getRng().pickOne({Function::Weight::None, Function::Weight::Hot, 71 | Function::Weight::Cold}))) 72 | return Modified::Yes; 73 | return Modified::No; 74 | } 75 | }; 76 | 77 | #endif // FUNCTIONMUTATOR_H 78 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/LiteralMaker.h: -------------------------------------------------------------------------------- 1 | #ifndef LITERALMAKER_H 2 | #define LITERALMAKER_H 3 | 4 | #include "UnsafeMutatorBase.h" 5 | 6 | /// Creates literal strings for specific types. 7 | struct LiteralMaker : UnsafeMutatorBase { 8 | 9 | LiteralMaker(MutatorData &input); 10 | 11 | /// Returns a random constant literal of the given type. 12 | Statement makeConstant(const StatementContext &, TypeRef t); 13 | 14 | private: 15 | /// Returns a valid constant string for the given type. 16 | std::string makeConstantStr(TypeRef tref); 17 | 18 | /// Known 'special' integers. 19 | /// 20 | /// Mostly powers of two and neighbors. 21 | std::vector specialIntegers; 22 | /// Known 'special' floats. 23 | /// 24 | /// Mostly powers of two and neighbors. 25 | std::vector specialFloats; 26 | 27 | /// Sets up the internal integer value list. 28 | void setupIntegers(); 29 | /// Sets up the internal float value list. 30 | void setupFloats(); 31 | }; 32 | 33 | #endif // LITERALMAKER_H 34 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/Simplifier.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLIFIER_H 2 | #define SIMPLIFIER_H 3 | 4 | #include "LiteralMaker.h" 5 | #include "StatementContext.h" 6 | #include "UnsafeMutatorBase.h" 7 | 8 | /// Performs simplification of code segments. 9 | /// 10 | /// All modifiations here are expected to produce 11 | /// less (== 'simpler') code. 12 | class Simplifier : UnsafeMutatorBase { 13 | typedef Statement::Kind StmtKind; 14 | LiteralMaker literals; 15 | 16 | public: 17 | Simplifier(MutatorData &input); 18 | 19 | /// Simplifies the given statement in-place. 20 | /// @return True if the code was simplified. 21 | bool simplifyStmt(StatementContext context, const Statement &parent, 22 | Statement &s); 23 | 24 | /// Simplifies the given compound in-place. 25 | /// @return True if the code was simplified. 26 | bool simplifyCompound(Statement &s); 27 | }; 28 | 29 | #endif // SIMPLIFIER_H 30 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/Snippets.h: -------------------------------------------------------------------------------- 1 | #ifndef SNIPPETS_H 2 | #define SNIPPETS_H 3 | 4 | #include "LiteralMaker.h" 5 | #include "TypeCreator.h" 6 | #include "UnsafeMutatorBase.h" 7 | 8 | /// Creates random code from a predefined set of code snippets. 9 | class Snippets : UnsafeMutatorBase { 10 | LiteralMaker literals; 11 | TypeCreator tc; 12 | Statement createSnippetImpl(const StatementContext &s); 13 | 14 | public: 15 | Snippets(MutatorData &input) 16 | : UnsafeMutatorBase(input), literals(input), tc(input) {} 17 | 18 | /// Create a random predefined piece of code. 19 | Statement createSnippet(const StatementContext &s) { 20 | Statement res = createSnippetImpl(s); 21 | p.verifySelf(); 22 | return res; 23 | } 24 | }; 25 | 26 | #endif // SNIPPETS_H 27 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/StatementContext.h: -------------------------------------------------------------------------------- 1 | #ifndef STATEMENTCONTEXT_H 2 | #define STATEMENTCONTEXT_H 3 | 4 | #include "scc/program/Function.h" 5 | #include "scc/program/Statement.h" 6 | #include "scc/program/Variable.h" 7 | #include 8 | #include 9 | 10 | /// Provides information that helps with transforming/inspecting a statement. 11 | /// 12 | /// The information here is usually information that is not stored in the 13 | /// statement itself (for space/complexity reasons). 14 | struct StatementContext { 15 | std::unordered_map variables; 16 | std::optional getVar(NameID id) { 17 | auto iter = variables.find(id); 18 | if (iter == variables.end()) 19 | return {}; 20 | return iter->second; 21 | } 22 | 23 | /// Whether the statement is within a loop. 24 | bool inLoop = false; 25 | /// The return type of the containing function. 26 | TypeRef returnType = Void(); 27 | 28 | const Function *function = nullptr; 29 | 30 | /// Returns the function body of the containing 31 | /// function. If we're in the global scope it 32 | /// returns an empty statement to simplify 33 | /// usage. 34 | const Statement &getFuncBody() const { 35 | if (function) 36 | return function->getBody(); 37 | return fakeBody; 38 | } 39 | 40 | TypeRef getRetType() const { return returnType; } 41 | 42 | StatementContext(const Function &f) : function(&f) {} 43 | 44 | /// Expands the context with the information added 45 | /// by the given statement. E.g., a variable declaration 46 | /// will add a mapping from name to variable to 47 | /// the context. 48 | void expandWithStmt(const Statement &s) { 49 | if (s.getKind() == Statement::Kind::VarDecl || 50 | s.getKind() == Statement::Kind::VarDef) { 51 | Variable v(s.getVariableType(), s.getDeclaredVarID()); 52 | variables[v.getName()] = v; 53 | } 54 | } 55 | 56 | /// Returns the StatementContext for a global 57 | /// variable initializer. 58 | static StatementContext Global() { return StatementContext(); } 59 | 60 | private: 61 | StatementContext() = default; 62 | Statement fakeBody; 63 | }; 64 | #endif // STATEMENTCONTEXT_H 65 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/StatementCreator.h: -------------------------------------------------------------------------------- 1 | #ifndef STATEMENTCREATOR_H 2 | #define STATEMENTCREATOR_H 3 | 4 | #include "Canonicalizer.h" 5 | #include "FunctionMutator.h" 6 | #include "Snippets.h" 7 | #include "TypeCreator.h" 8 | #include "UnsafeMutatorBase.h" 9 | #include "scc/program/GlobalVar.h" 10 | #include "scc/utils/Counter.h" 11 | 12 | /// Creates new random statements. 13 | class StatementCreator : public UnsafeMutatorBase { 14 | TypeCreator tc; 15 | FunctionMutator fm; 16 | 17 | std::vector stmtStack; 18 | /// How deeply we can nest expressions in one mutation step. 19 | RecursionLimit exprRecursionLimit = 2; 20 | /// How deeply we can nest statements in one mutation step. 21 | RecursionLimit stmtRecursionLimit = 3; 22 | /// How deeply we can neste functions in one mutation step. 23 | RecursionLimit funcRecursionLimit = 3; 24 | 25 | /// The snippet maker. 26 | Snippets snippets; 27 | /// Creates literals for types. 28 | LiteralMaker literalMaker; 29 | 30 | /// Verifies the code/program and returns it. 31 | template T verify(T s) { 32 | s.verifySelf(p); 33 | return s; 34 | } 35 | 36 | public: 37 | StatementCreator(MutatorData &input); 38 | 39 | /// Queues a statement to be recycled later. 40 | void pushStmtOnStack(Statement s) { stmtStack.push_back(s); } 41 | 42 | /// Returns an array variable initializer. 43 | Statement makeArrayInit(StatementContext &context, TypeRef tref) { 44 | auto verifyScope = p.queueVerify(); 45 | tref = stripCV(tref); 46 | TypeRef base = getType(tref).getBase(); 47 | SCCAssert(base != tref, "Cycle in array types?"); 48 | 49 | std::vector values; 50 | for (unsigned i = 0; i < types.get(tref).getArraySize(); ++i) { 51 | values.push_back(makeConstant(context, base)); 52 | if (decision(Frag::DontFillArrayConstant)) 53 | break; 54 | } 55 | return verify(Statement::ConstantArray(values, tref)); 56 | } 57 | 58 | /// Returns the code that initializes a variable of type t. 59 | Statement makeVarInit(StatementContext &c, TypeRef t) { 60 | auto verifyScope = p.queueVerify(); 61 | if (getType(t).getKind() == Type::Kind::Array) 62 | return makeArrayInit(c, t); 63 | return verify(makeConstant(c, t)); 64 | } 65 | 66 | /// Given an incomplete function, adds it to the program and gives it 67 | /// a body if possible. 68 | Function *finishFunctionCreation(std::unique_ptr &&f) { 69 | auto verifyScope = p.queueVerify(); 70 | if (decision(Frag::InitWithFuncAttrs)) 71 | fm.randomizeFuncAttrs(*f); 72 | auto recLimit = funcRecursionLimit.scope(); 73 | 74 | // Check if we reached the recursion limit for function scopes. 75 | // This avoids that a function-generation heavy strategy spirals out of 76 | // control. 77 | if (recLimit.reached()) { 78 | auto verifyScope = p.queueVerify(); 79 | f->setBody(Statement::CompoundStmt({})); 80 | } else { 81 | auto verifyScope = p.queueVerify(); 82 | f->setBody(makeFunctionBody(*f)); 83 | } 84 | // Might make the function static or noexcept. 85 | f->isStatic = decision(Frag::FunctionIsStatic); 86 | if (p.getLangOpts().isCxx()) 87 | f->isNoExcept = decision(Frag::FunctionIsNoExcept); 88 | return &p.add(std::move(f)); 89 | } 90 | 91 | /// Creates a function with the given signature (encoded as a function ptr 92 | /// type). 93 | Function *createFunctionWithType(TypeRef tref) { 94 | auto verifyScope = p.queueVerify(); 95 | Type t = getType(tref); 96 | std::vector args; 97 | for (TypeRef arg : t.getArgs()) 98 | args.push_back(Variable(arg, idents.makeNewID("arg"))); 99 | // TODO: Make this variadic? 100 | 101 | auto f = std::make_unique(t.getFuncReturnType(), 102 | idents.makeNewID("func"), args); 103 | return finishFunctionCreation(std::move(f)); 104 | } 105 | 106 | /// Creates a function with the given type as a return type. 107 | Function *createFunctionWithReturnType(TypeRef t) { 108 | auto verifyScope = p.queueVerify(); 109 | std::vector args; 110 | 111 | for (unsigned i = 0; i < getRng().getBelow(8); ++i) { 112 | auto verifyScope = p.queueVerify(); 113 | args.push_back( 114 | Variable(tc.getExistingDefinedType(), idents.makeNewID("arg"))); 115 | } 116 | 117 | auto f = std::make_unique(t, idents.makeNewID("func"), args); 118 | return finishFunctionCreation(std::move(f)); 119 | } 120 | 121 | /// Returns any function. 122 | Function *getAnyFunction() { 123 | std::vector options; 124 | for (Decl *d : p.getDeclList()) { 125 | if (d->getKind() == Decl::Kind::Function) 126 | options.push_back(static_cast(d)); 127 | } 128 | if (options.empty()) 129 | return createFunctionWithReturnType(tc.getReturnType()); 130 | return getRng().pickOneVec(options); 131 | } 132 | 133 | /// Create a goto. 134 | Statement makeGoto(StatementContext &context) { 135 | SCCAssert(context.function, "Can only do goto in function"); 136 | if (!context.function) 137 | return makeStmt(context); 138 | 139 | auto labels = getAllLabels(*context.function); 140 | if (labels.empty()) 141 | return makeStmt(context); 142 | 143 | return Statement::Goto(getRng().pickOneVec(labels).id); 144 | } 145 | 146 | /// Creates a random 'throw' statement. 147 | Statement makeThrow(StatementContext &context) { 148 | auto verifyScope = p.queueVerify(); 149 | return Statement::Throw(makeExpr(context, tc.getDefinedType())); 150 | } 151 | 152 | /// Creates a random 'catch' statement. 153 | Statement makeCatch(StatementContext context) { 154 | return Statement::Catch(tc.getDefinedType(), idents.makeNewID("c"), 155 | makeStmt(context)); 156 | } 157 | 158 | /// Creates a catch all statement 'catch (...)'. 159 | Statement makeCatchAll(StatementContext context) { 160 | return Statement::CatchAll(makeStmt(context)); 161 | } 162 | 163 | /// Creates a try statement with potentially several catch statements. 164 | Statement makeTry(StatementContext &context) { 165 | auto verifyScope = p.queueVerify(); 166 | std::vector catches; 167 | for (unsigned i = 0; i < getRng().getBelow(4U); ++i) 168 | catches.push_back(makeCatch(context)); 169 | if (decision(Frag::CatchAll)) 170 | catches.push_back(makeCatchAll(context)); 171 | return Statement::Try(makeStmt(context), catches); 172 | } 173 | 174 | /// Creates a random inline assembly statement. 175 | Statement makeAsm(const StatementContext &context) { 176 | // TODO: We could do more here, but it's out of scope for sanitizers 177 | // to this probably just creates false positives. 178 | return Statement::Asm("nop"); 179 | } 180 | 181 | /// Creates a call to function 'f' with random arguments. 182 | Statement makeCallToFunc(const StatementContext &context, Function *f) { 183 | auto verifyScope = p.queueVerify(); 184 | std::vector args; 185 | for (Variable arg : f->getArgs()) 186 | args.push_back(makeExpr(context, arg.getType())); 187 | 188 | if (f->isVariadic()) 189 | for (unsigned i = 0; i < getRng().getBelow(10); ++i) 190 | args.push_back(makeExpr(context, tc.getAnyIntOrPtrOrFloatType())); 191 | 192 | return Statement::Call(f->getReturnType(), f->getNameID(), args); 193 | } 194 | 195 | /// Create a call that results in a value of the given type. 196 | Statement makeCall(const StatementContext &context, TypeRef t) { 197 | auto verifyScope = p.queueVerify(); 198 | // Maybe we can call a function pointer here? 199 | if (decision(Frag::CallFuncPtr)) { 200 | TypeRef funcPtrT = tc.makeNewFuncPtrTypeWithResult(t); 201 | Statement funcPtr = makeExpr(context, funcPtrT); 202 | std::vector args; 203 | for (TypeRef arg : types.get(funcPtrT).getArgs()) 204 | args.push_back(makeExpr(context, arg)); 205 | return Statement::IndirectCall(t, funcPtr, args); 206 | } 207 | Function *f = createFunctionWithReturnType(t); 208 | std::vector args; 209 | for (Variable arg : f->getArgs()) 210 | args.push_back(makeExpr(context, arg.getType())); 211 | return Statement::Call(t, f->getNameID(), args); 212 | } 213 | 214 | /// Create a constant of the given type. 215 | Statement makeConstant(const StatementContext &c, TypeRef t) { 216 | auto verifyScope = p.queueVerify(); 217 | return literalMaker.makeConstant(c, t); 218 | } 219 | 220 | /// Create a global variable of the given type. 221 | GlobalVar *makeGlobal(TypeRef t) { 222 | auto g = std::make_unique(t, newID("global")); 223 | g->is_static = decision(Frag::VarIsStatic); 224 | if (getType(t).expectsVarInitializer(types) || decision(Frag::InitGlobal)) { 225 | // No context with variables available for globals. 226 | StatementContext c = StatementContext::Global(); 227 | if (getType(t).isArray()) 228 | g->setInit(makeArrayInit(c, t)); 229 | else 230 | g->setInit(makeConstant(c, t)); 231 | } 232 | return &p.add(std::move(g)); 233 | } 234 | 235 | /// Returns a global that might already exist. 236 | GlobalVar *makeOrFindGlobal(TypeRef t) { 237 | for (Decl *d : p.getDeclList()) { 238 | if (d->getKind() == Decl::Kind::GlobalVar) { 239 | GlobalVar *g = static_cast(d); 240 | if (g->getAsVar().getType() == t) { 241 | if (decision(Frag::PickExistingGlobal)) 242 | return g; 243 | } 244 | } 245 | } 246 | return makeGlobal(t); 247 | } 248 | 249 | /// Creates an lvalue expression of the given type. 250 | Statement makeLValue(const StatementContext &context, TypeRef t) { 251 | if (getType(t).getKind() == Type::Kind::Pointer && 252 | decision(Frag::TryDerefVar)) { 253 | return Statement::Deref(t, makeLValue(context, tc.getPtrTypeOf(t))); 254 | } 255 | return makeVarRef(context, t); 256 | } 257 | 258 | /// Create a binary operator. 259 | Statement makeBinary(const StatementContext &context, TypeRef t) { 260 | Type type = getType(t); 261 | StmtKind kind; 262 | TypeRef lhsT = Void(); 263 | TypeRef rhsT = Void(); 264 | if (builtin.isIntType(t)) { 265 | kind = getRng().pickOneVec(Statement::getIntArithmeticOps()); 266 | lhsT = tc.getAnyIntType(/*allowConst=*/kind != Stmt::Kind::Assign); 267 | rhsT = tc.getAnyIntType(/*allowConst=*/true); 268 | } else if (builtin.isFloatType(t)) { 269 | lhsT = tc.getAnyIntOrFloatType(); 270 | rhsT = tc.getAnyIntOrFloatType(); 271 | kind = getRng().pickOneVec(Statement::getFloatArithmeticOps()); 272 | } else if (type.getKind() == Type::Kind::Pointer) { 273 | lhsT = t; 274 | rhsT = tc.getAnyIntType(/*allowConst=*/true); 275 | kind = getRng().pickOneVec(Statement::getPtrArithmeticOps()); 276 | } else { 277 | SCCError("Neither ptr nor int binary operator requested?"); 278 | return makeConstant(context, t); 279 | } 280 | 281 | if (kind == StmtKind::Assign) 282 | return Statement::BinaryOp(p, kind, makeLValue(context, lhsT), 283 | makeExpr(context, rhsT)); 284 | return Statement::BinaryOp(p, kind, makeExpr(context, lhsT), 285 | makeExpr(context, rhsT)); 286 | } 287 | 288 | Statement makeCxxNewExpr(const StatementContext &context, TypeRef t) { 289 | return Statement::New(t, {}); 290 | } 291 | 292 | Statement makeDelete(const StatementContext &context) { 293 | return Statement::Delete(makeExpr(context, tc.getPtrType())); 294 | } 295 | 296 | Statement makeDeref(const StatementContext &context, TypeRef t) { 297 | return Statement::Deref( 298 | t, makeExpr(context, 299 | types.getOrCreateDerived(idents, Type::Kind::Pointer, t))); 300 | } 301 | 302 | /// Make a subscript statement (that is, a 'array[x]' statement). 303 | Statement makeSubscript(const StatementContext &context, TypeRef t) { 304 | Statement base = makeExpr( 305 | context, types.getOrCreateDerived(idents, Type::Kind::Pointer, t)); 306 | Statement index = makeExpr(context, tc.getAnyIntType(/*allowConst=*/true)); 307 | return Statement::Subscript(t, base, index); 308 | } 309 | 310 | /// Returns true if the 'from' type can be converted to the given 'to' type. 311 | bool canTypeConvertTo(TypeRef from, TypeRef to) { 312 | // Same type, so fine to convert. 313 | if (from == to) 314 | return true; 315 | const Type &fromT = types.get(from); 316 | const Type &toT = types.get(to); 317 | 318 | // Arrays can decay to pointers. 319 | if (fromT.getKind() == Type::Kind::Array && 320 | toT.getKind() == Type::Kind::Pointer) 321 | return canTypeConvertTo(fromT.getBase(), toT.getBase()); 322 | // CV-qualifiers can be added implicitly. 323 | if (toT.isCVQualified()) 324 | return canTypeConvertTo(from, toT.getBase()); 325 | return false; 326 | } 327 | 328 | /// Returns a variable reference. 329 | Statement makeVarRef(const StatementContext &context, TypeRef t) { 330 | // Check if we can use a variable that is in context. 331 | if (decision(Frag::PickLocalVar)) 332 | for (const auto &v : context.variables) 333 | if (canTypeConvertTo(v.second.getType(), t)) 334 | return Statement::LocalVarRef(v.second); 335 | return Statement::GlobalVarRef(makeOrFindGlobal(t)->getAsVar()); 336 | } 337 | 338 | std::vector callableBuiltins() const; 339 | 340 | /// Creates a random expression evaluating to the given type. 341 | Statement makeExprImpl(const StatementContext &context, TypeRef t) { 342 | SCCAssert(types.isValid(t), "Expression with invalid type?"); 343 | 344 | auto verifyScope = p.queueVerify(); 345 | auto recLimit = exprRecursionLimit.scope(); 346 | if (recLimit.reached()) 347 | return verify(makeConstant(context, t)); 348 | 349 | t = stripCV(t); 350 | SCCAssert(types.isValid(t), "Type got invalid after stripping?"); 351 | 352 | const bool isPointer = types.get(t).getKind() == Type::Kind::Pointer; 353 | 354 | if (decision(Frag::CallBuiltin)) { 355 | auto verifyScope = p.queueVerify(); 356 | for (auto counter : count::upTo(10)) { 357 | BuiltinFunctions::Kind kind = getRng().pickOneVec(callableBuiltins()); 358 | const TypeRef returnT = builtinFuncs.getReturnType(p, kind); 359 | if (returnT == t) { 360 | Function *f = builtinFuncs.get(p, kind); 361 | return verify(makeCallToFunc(context, f)); 362 | } 363 | if (types.get(returnT).isPointer() && isPointer) { 364 | Function *f = builtinFuncs.get(p, kind); 365 | return Statement::Cast(t, verify(makeCallToFunc(context, f))); 366 | } 367 | } 368 | } 369 | 370 | // If we expect a void pointer, just pick any pointer/int and 371 | // cast it to a void pointer. 372 | if (builtin.isVoidPtr(t)) 373 | return verify( 374 | Statement::Cast(t, makeExprImpl(context, tc.getAnyIntOrPtrType()))); 375 | 376 | // For float/int/pointer we have several options. 377 | if (builtin.isFloatType(t) || builtin.isIntType(t) || isPointer) { 378 | auto verifyScope = p.queueVerify(); 379 | enum Options { 380 | Constant, 381 | Bin, 382 | Call, 383 | Var, 384 | Cast, 385 | Deref, 386 | Subscript, 387 | AddrOf, 388 | New 389 | }; 390 | std::vector options = {Constant, Subscript, Bin, Call, Var, 391 | Var, Var, Cast, Deref}; 392 | // If this is a pointer, we can also take the address of something. 393 | if (isPointer) { 394 | options.push_back(AddrOf); 395 | if (p.getLangOpts().isCxx()) 396 | options.push_back(New); 397 | } 398 | // Pick one option. 399 | switch (getRng().pickOneVec(options)) { 400 | case Constant: 401 | return verify(makeConstant(context, t)); 402 | case Bin: 403 | return verify(makeBinary(context, t)); 404 | case Deref: 405 | return verify(makeDeref(context, t)); 406 | case Subscript: 407 | return verify(makeSubscript(context, t)); 408 | case New: 409 | return verify(makeCxxNewExpr(context, t)); 410 | case Call: 411 | // Recurse and hope we pick another case. 412 | if (!isValidReturnType(t)) 413 | return verify(makeExprImpl(context, t)); 414 | return verify(makeCall(context, t)); 415 | case Var: 416 | return verify(makeVarRef(context, t)); 417 | case Cast: { 418 | if (isPointer) 419 | return verify(Statement::Cast( 420 | t, makeExprImpl(context, tc.getAnyIntOrPtrType()))); 421 | return verify(Statement::Cast( 422 | t, makeExprImpl(context, tc.getAnyIntType(/*allowConst=*/true)))); 423 | } 424 | case AddrOf: 425 | return verify( 426 | Statement::AddrOf(t, makeLValue(context, getType(t).getBase()))); 427 | } 428 | } 429 | 430 | if (getType(t).getKind() == Type::Kind::Array) { 431 | enum { VarRef }; 432 | switch (getRng().pickOne({VarRef})) { 433 | case VarRef: 434 | return verify(makeVarRef(context, t)); 435 | } 436 | } 437 | 438 | if (getType(t).getKind() == Type::Kind::FunctionPointer) { 439 | enum { AddrOf, Constant }; 440 | switch (getRng().pickOne({AddrOf, Constant})) { 441 | case Constant: 442 | return verify(makeConstant(context, t)); 443 | case AddrOf: 444 | return verify( 445 | Statement::AddrOfFunc(t, createFunctionWithType(t)->getNameID())); 446 | } 447 | } 448 | 449 | if (getType(t).getKind() == Type::Kind::Record) { 450 | enum { Constant, GlobalVarRef }; 451 | switch (getRng().pickOne({Constant, Constant})) { 452 | case Constant: 453 | return verify(makeConstant(context, t)); 454 | case GlobalVarRef: 455 | return verify(makeVarRef(context, t)); 456 | } 457 | } 458 | 459 | if (t == Void()) 460 | return makeCall(context, t); 461 | 462 | // We should never reach this. 463 | assert(false); 464 | return verify(makeConstant(context, t)); 465 | } 466 | 467 | Statement makeExpr(const StatementContext &context, TypeRef t) { 468 | Statement s = makeExprImpl(context, t); 469 | s.verifySelf(p); 470 | SCCAssert(s.isExpr(), "makeExpr returned a statement?"); 471 | return s; 472 | } 473 | 474 | Statement makeReturn(const StatementContext &context) { 475 | auto verifyScope = p.queueVerify(); 476 | if (context.getRetType() == Void()) 477 | return Statement::VoidReturn(); 478 | return Statement::Return(makeExpr(context, context.getRetType())); 479 | } 480 | 481 | Statement makeIf(const StatementContext &context) { 482 | auto verifyScope = p.queueVerify(); 483 | Statement cond = makeExpr(context, tc.getBoolType()); 484 | return Statement::If(cond, makeCompoundStmt(context)); 485 | } 486 | 487 | Statement makeWhile(StatementContext context) { 488 | auto verifyScope = p.queueVerify(); 489 | Statement cond = makeExpr(context, tc.getBoolType()); 490 | context.inLoop = true; 491 | return Statement::While(cond, makeCompoundStmt(context)); 492 | } 493 | 494 | /// Declare a random variable. 495 | Statement makeVarDecl(StatementContext &context, bool isDefinition) { 496 | auto verifyScope = p.queueVerify(); 497 | TypeRef t = Void(); 498 | for (unsigned i = 0; i < 1000; ++i) { 499 | if (isDefinition) 500 | t = tc.getDefinedType(); 501 | else { 502 | t = tc.getDefinedNonConstType(); 503 | assert(!types.isConst(t)); 504 | } 505 | if (isDefinition) 506 | break; 507 | // If we ended up with a type that expects an initializer 508 | // but we have a declaration (not a definition) then just 509 | // retry. 510 | if (types.get(t).expectsVarInitializer(types)) 511 | continue; 512 | } 513 | 514 | NameID id = newID("var"); 515 | Variable info(t, id); 516 | 517 | if (isDefinition) { 518 | if (getType(t).getKind() == Type::Kind::Array) { 519 | Statement res = Statement::VarDef(t, id, makeArrayInit(context, t)); 520 | context.variables[id] = info; 521 | return res; 522 | } else { 523 | Statement res = Statement::VarDef(t, id, makeExpr(context, t)); 524 | context.variables[id] = info; 525 | return res; 526 | } 527 | } 528 | context.variables[id] = info; 529 | return Statement::VarDecl(t, id); 530 | } 531 | 532 | struct JumpLabel { 533 | NameID id = InvalidName; 534 | JumpLabel() = default; 535 | explicit JumpLabel(NameID id) : id(id) {} 536 | }; 537 | 538 | /// Returns all goto labels in the given function. 539 | std::vector getAllLabels(const Function &f) { 540 | std::vector res; 541 | auto children = f.getBody().getAllChildren(); 542 | for (const Statement::StmtAndParent &p : children) { 543 | if (p.stmt->getKind() == StmtKind::GotoLabel) 544 | res.emplace_back(p.stmt->getJumpTarget()); 545 | } 546 | return res; 547 | } 548 | 549 | std::string makeStmtAttr() { 550 | return getRng().pickOne({"[[likely]]", "[[unlikely]]"}); 551 | } 552 | 553 | Statement makeBuiltinCallStmt(StatementContext &context) { 554 | BuiltinFunctions::Kind kind = getRng().pickOneVec(callableBuiltins()); 555 | Function *f = builtinFuncs.get(p, kind); 556 | return wrapExprInStmt(makeCallToFunc(context, f)); 557 | } 558 | 559 | /// Creates a random statement. 560 | Statement makeStmtImpl(StatementContext &context, bool avoidDecl = false) { 561 | auto verifyScope = p.queueVerify(); 562 | 563 | // Maybe we can just create a snippet? 564 | if (decision(Frag::UseSnippet)) 565 | return snippets.createSnippet(context); 566 | 567 | // Maybe we can recycle an old piece of code we threw away before. 568 | if (decision(Frag::UseMutatedStmtAsChild) && !stmtStack.empty()) { 569 | Statement res = stmtStack.back(); 570 | stmtStack.pop_back(); 571 | return verify(res); 572 | } 573 | 574 | // Ask if we just want to call a builtin function. 575 | if (decision(Frag::ForceCallBuiltinStmt)) 576 | return verify(makeBuiltinCallStmt(context)); 577 | 578 | // If we reach this, then we could be in a recursion. Check the recursion 579 | // limit and return an empty statement as a fallback. 580 | auto recLimit = stmtRecursionLimit.scope(); 581 | if (recLimit.reached()) 582 | return verify(Statement::Empty()); 583 | 584 | enum Opt { 585 | Return, 586 | Expr, 587 | If, 588 | While, 589 | VarDecl, 590 | VarDef, 591 | Break, 592 | Asm, 593 | Call, 594 | Try, 595 | Throw, 596 | Delete, 597 | Goto, 598 | Label, 599 | Compound 600 | }; 601 | std::vector toPick = {Return, Expr, If, While, VarDecl, 602 | Call, VarDef, Asm, Break, Goto, 603 | Label, Compound, Compound, Compound}; 604 | // For C++, we can generate C++ generic statements. 605 | if (p.getLangOpts().isCxx()) { 606 | if (false) { // TODO: This frequently causes miscompiles. 607 | toPick.push_back(Try); 608 | toPick.push_back(Throw); 609 | } 610 | toPick.push_back(Delete); 611 | } 612 | 613 | // Pick one of the options and branch to one of the specific generation 614 | // functions. 615 | switch (getRng().pickOneVec(toPick)) { 616 | case Return: 617 | return verify(makeReturn(context)); 618 | case Compound: 619 | return verify(makeCompoundStmt(context)); 620 | case If: 621 | return verify(makeIf(context)); 622 | case Break: 623 | if (context.inLoop) 624 | return verify(Statement::Break()); 625 | return verify(makeStmt(context)); 626 | case While: 627 | return verify(makeWhile(context)); 628 | case Delete: 629 | return verify(makeDelete(context)); 630 | case Asm: 631 | return verify(makeAsm(context)); 632 | case Try: 633 | return verify(makeTry(context)); 634 | case Throw: 635 | return verify(makeThrow(context)); 636 | case Goto: { 637 | Statement s = verify(makeGoto(context)); 638 | if (s.getKind() == Statement::Kind::Empty) 639 | return verify(makeStmt(context, avoidDecl)); 640 | break; 641 | } 642 | case Label: 643 | return verify(Statement::GotoLabel(idents.makeNewID("rng_lbl"))); 644 | case VarDecl: 645 | if (avoidDecl) 646 | return verify(makeStmt(context, avoidDecl)); 647 | return verify(makeVarDecl(context, false)); 648 | case VarDef: 649 | if (avoidDecl) 650 | return verify(makeStmt(context, avoidDecl)); 651 | return verify(makeVarDecl(context, true)); 652 | case Call: 653 | return wrapExprInStmt(makeCallToFunc(context, getAnyFunction())); 654 | case Expr: 655 | return wrapExprInStmt(makeExpr(context, tc.getDefinedType())); 656 | } 657 | 658 | return Statement::Empty(); 659 | } 660 | 661 | /// Creates a random statement. 662 | Statement makeStmt(StatementContext &context, bool avoidDecl = false) { 663 | Statement s = makeStmtImpl(context, avoidDecl); 664 | s.verifySelf(p); 665 | SCCAssert(s.isStmt(), "makeStmt returned expression?"); 666 | return s; 667 | } 668 | 669 | /// Creates a random compound statement (that is, code in curly brackets). 670 | Statement makeCompoundStmt(StatementContext context) { 671 | std::vector children; 672 | for (unsigned i = 0; i < getRng().getBelow(16); ++i) { 673 | auto c = makeStmt(context); 674 | if (c.getKind() == StmtKind::Empty) 675 | break; 676 | context.expandWithStmt(c); 677 | children.push_back(c); 678 | } 679 | return Statement::CompoundStmt(children); 680 | } 681 | 682 | /// Recreates the statement context for f. 683 | StatementContext getContextForFunction(const Function &f) { 684 | StatementContext context(f); 685 | // Make function args available. 686 | for (const Variable &arg : f.getArgs()) { 687 | context.variables[arg.getName()] = arg; 688 | // Don't add argv which has variable contents and makes test cases 689 | // unstable. 690 | // FIXME: This actually helped in the past with making interesting test 691 | // cases, so we should find a way to re-add this properly. Not sure yet 692 | // what is the right thing to do here. 693 | if (f.isMain(p)) 694 | break; 695 | } 696 | context.returnType = f.getReturnType(); 697 | return context; 698 | } 699 | 700 | /// Creates a random function body for the given function. 701 | Statement makeFunctionBody(Function &f) { 702 | auto verifyScope = p.queueVerify(); 703 | 704 | StatementContext context = getContextForFunction(f); 705 | std::vector children; 706 | for (unsigned i = 0; i < 1 + getRng().getBelow(16); ++i) 707 | children.push_back(makeStmt(context)); 708 | 709 | if (decision(Frag::EnsureReturnInFunc)) 710 | children.push_back(makeReturn(context)); 711 | 712 | Statement body = Statement::CompoundStmt(children); 713 | body.verifySelf(p); 714 | if (auto canonicalized = Canonicalizer::canonicalizeStmt(body)) 715 | body = *canonicalized; 716 | 717 | return body; 718 | } 719 | }; 720 | 721 | #endif // STATEMENTCREATOR_H 722 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/StatementMutator.h: -------------------------------------------------------------------------------- 1 | #ifndef STATEMENTMUTATOR_H 2 | #define STATEMENTMUTATOR_H 3 | 4 | #include "CodeMoving.h" 5 | #include "LiteralMaker.h" 6 | #include "Simplifier.h" 7 | #include "StatementCreator.h" 8 | #include "UnsafeMutatorBase.h" 9 | 10 | /// Mutates an existing statement in various ways. 11 | class StatementMutator : public UnsafeMutatorBase { 12 | LiteralMaker literalMaker; 13 | Simplifier simplifier; 14 | StatementCreator sc; 15 | CodeMoving mover; 16 | 17 | public: 18 | StatementMutator(MutatorData &input); 19 | 20 | /// Mutates a compound statement. 21 | /// 22 | /// Returns true iff the given statement was modified. 23 | bool mutateCompound(StatementContext context, Statement &s) { 24 | // This only modifies compounds. 25 | if (s.getKind() != StmtKind::Compound) 26 | return false; 27 | 28 | // The new child statements. 29 | std::vector children; 30 | 31 | // Choose a random index to insert a new statement. 32 | size_t index = 0; 33 | const size_t insertAfter = getRng().getBelow(s.getNumChildren()); 34 | for (const Statement &c : s) { 35 | // Make sure the code we generate down below knows about variables etc. 36 | context.expandWithStmt(c); 37 | children.push_back(c); 38 | if (index == insertAfter) 39 | children.push_back(sc.makeStmt(context)); 40 | ++index; 41 | } 42 | 43 | // Form a new compound statement with the generated children. 44 | s = Statement::CompoundStmt(children); 45 | return true; 46 | } 47 | 48 | /// Replaces the given statement with a compound statement just containing 49 | /// its direct children. 50 | bool promoteChildren(Statement &s) { 51 | std::vector newChildren; 52 | for (auto &c : s.getChildren()) 53 | newChildren.push_back(ensureStmt(c)); 54 | s = Statement::CompoundStmt(newChildren); 55 | return true; 56 | } 57 | 58 | /// Replaces the given statement with one of its child statements. 59 | bool promoteChild(Statement &s) { 60 | const auto &children = s.getChildren(); 61 | if (children.empty()) 62 | return false; 63 | Statement child = getRng().pickOneVec(children); 64 | s = ensureStmt(s); 65 | return true; 66 | } 67 | 68 | /// Surrounds the statement with random code. 69 | bool wrapInCompound(StatementContext context, Statement &s) { 70 | s = Statement::CompoundStmt( 71 | {sc.makeStmt(context), s, sc.makeStmt(context)}); 72 | return true; 73 | } 74 | 75 | /// Randomly mutates the given statement. 76 | /// @param context The StatementContext that can be used. 77 | /// @param parent The parent of the given statement 78 | /// @param s The statement to mutate. 79 | /// @return Whether the statement has been modified. 80 | Modified mutateStatement(StatementContext context, const Statement &parent, 81 | Statement &s) { 82 | auto verifyScope = p.queueVerify(); 83 | 84 | // Check whether we can modify this statement. 85 | if (!canMutate(context, parent, s)) 86 | return Modified::No; 87 | 88 | // Use compound specific mutations. 89 | if (decision(Frag::MutateCompound) && mutateCompound(context, s)) 90 | return Modified::Yes; 91 | 92 | // Check for variable declarations/definitions. 93 | const bool isVar = s == Stmt::Kind::VarDecl || s == Stmt::Kind::VarDef; 94 | 95 | if (s.isStmt()) { 96 | if (isVar) { 97 | bool canBeDecl = 98 | !types.get(s.getVariableType()).expectsVarInitializer(types); 99 | // Swap a variable definition with a declaration. 100 | if (canBeDecl && decision(Frag::SwapDefAndDecl)) { 101 | if (s == Stmt::Kind::VarDecl) { 102 | s = Stmt::VarDef(s.getVariableType(), s.getDeclaredVarID(), 103 | sc.makeExpr(context, s.getVariableType())); 104 | } else { 105 | s = Stmt::VarDecl(s.getVariableType(), s.getDeclaredVarID()); 106 | } 107 | return Modified::Yes; 108 | } 109 | // if the declared variable is used anywhere, then we can't modify 110 | // further on the off-chance that we might delete it and render 111 | // the references to the variable invalid. 112 | if (isVarUsed(parent, s)) 113 | return Modified::No; 114 | } 115 | 116 | // Try several generic mutation rules. 117 | if (decision(Frag::PromoteChild) && promoteChild(s)) 118 | return Modified::Yes; 119 | if (decision(Frag::PromoteChildren) && promoteChildren(s)) 120 | return Modified::Yes; 121 | if (decision(Frag::WrapInCompound) && wrapInCompound(context, s)) 122 | return Modified::Yes; 123 | 124 | // Regenerate the statement. Before that, push it on a stack so that 125 | // the mutator might recycle it. 126 | sc.pushStmtOnStack(s); 127 | s = sc.makeStmt(context); 128 | return Modified::Yes; 129 | } 130 | 131 | // Regenerate an expression and overwrite the original code. 132 | SCCAssert(s.isExpr(), "Not an expression?"); 133 | SCCAssert(types.isValid(s.getEvalType()), "invalid eval type?"); 134 | s = sc.makeExpr(context, s.getEvalType()); 135 | return Modified::Yes; 136 | } 137 | 138 | /// Returns the reconstructed StatementContext (e.g., what variables are in 139 | /// scope for the given statement). 140 | std::optional 141 | rebuildStatementContextFor(StatementContext ctx, Statement &base, 142 | Statement &target) { 143 | if (base.getKind() == StmtKind::While) 144 | ctx.inLoop = true; 145 | 146 | for (Statement &s : base) { 147 | if (&s == &target) 148 | return ctx; 149 | ctx.expandWithStmt(s); 150 | if (auto nested = rebuildStatementContextFor(ctx, s, target)) 151 | return nested; 152 | } 153 | return ctx; 154 | } 155 | 156 | /// Mutates a random child of the given statement. 157 | Modified mutateRandomChild(StatementContext context, Statement &s) { 158 | // For simplicity, place an empty child in compound statements. 159 | // Note: This might lie about modification, but the empty statement doesn't 160 | // change any semantics so that is fine. 161 | if (s == Stmt::Kind::Compound && s.getNumChildren() == 0) 162 | s = Stmt::CompoundStmt({Stmt::Empty()}); 163 | 164 | auto verifyScope = p.queueVerify(); 165 | 166 | // Try simplifying compounds. 167 | if (simplifier.simplifyCompound(s)) 168 | return Modified::Yes; 169 | 170 | // Pick a random child of the given statement. 171 | std::vector allChildren = s.getAllChildren(); 172 | if (allChildren.empty()) 173 | return Modified::No; 174 | Statement::StmtAndParent toModify = getRng().pickOneVec(allChildren); 175 | 176 | // If we have an expression and we prefer modifying statements, then 177 | // try picking another one. 178 | while (toModify.stmt->isExpr()) { 179 | if (!decision(Frag::PreferModifyingStmtsOverExprs)) 180 | break; 181 | toModify = getRng().pickOneVec(allChildren); 182 | } 183 | 184 | // Build a statement context so we know which variables are in scope. 185 | context = *rebuildStatementContextFor(context, s, *toModify.stmt); 186 | 187 | // Try to just simplify the code and see if this helps. 188 | if (decision(Frag::SimplifyStmt)) 189 | return simplifier.simplifyStmt(context, *toModify.parent, *toModify.stmt) 190 | ? Modified::Yes 191 | : Modified::No; 192 | // Otherwise, try mutating the statement. 193 | if (decision(Frag::MutateFoundStatement)) 194 | return mutateStatement(context, *toModify.parent, *toModify.stmt); 195 | return Modified::No; 196 | } 197 | 198 | /// Mutates the body of the given function. 199 | Modified mutateFunctionBody(Function &f, Statement &newBody) { 200 | StatementContext context = sc.getContextForFunction(f); 201 | // Maybe try to just make a new function body. 202 | if (decision(Frag::RegenerateFunctionBody)) 203 | newBody = sc.makeCompoundStmt(context); 204 | // Mutate a random piece of code in the function. 205 | if (mutateRandomChild(context, newBody) == Modified::No) 206 | return Modified::No; 207 | // Clean up any weird artifacts from the mutation process. 208 | if (auto canonicalized = Canonicalizer::canonicalizeStmt(newBody)) 209 | newBody = *canonicalized; 210 | return Modified::Yes; 211 | } 212 | }; 213 | 214 | #endif // STATEMENTMUTATOR_H 215 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/Type.def: -------------------------------------------------------------------------------- 1 | TYPEDECL(S8, "signed char", 8) 2 | TYPEDECL(S16, "signed short", 16) 3 | TYPEDECL(S32, "signed int", 32) 4 | TYPEDECL(S64, "signed long", 64) 5 | TYPEDECL(U8, "unsigned char", 8) 6 | TYPEDECL(U16, "unsigned short", 16) 7 | TYPEDECL(U32, "unsigned int", 32) 8 | TYPEDECL(U64, "unsigned long", 64) 9 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/TypeCreator.h: -------------------------------------------------------------------------------- 1 | #ifndef TYPECREATOR_H 2 | #define TYPECREATOR_H 3 | 4 | #include "UnsafeMutatorBase.h" 5 | #include "scc/mutator-utils/RecursionLimit.h" 6 | #include "scc/program/Program.h" 7 | #include "scc/program/RecordDecl.h" 8 | 9 | /// Creates new types in the program. 10 | class TypeCreator : UnsafeMutatorBase { 11 | // Define some recursion limits that avoid crashes. 12 | 13 | /// How many nested types we are allowed to create in one go. 14 | RecursionLimit typeRecursionLimit = 3; 15 | /// How many nested records we are allowed to create in on ego. 16 | RecursionLimit recordRecursionLimit = 3; 17 | 18 | /// Limits the amount of types that are created by a single TypeCreator. 19 | /// 20 | /// This is necessary to avoid a potential explosion of types given a bad RNG 21 | /// sequence or a bad mutation strategy. The biggest thing we're trying to 22 | /// avoid is creating a large sequence of function pointer types (as each 23 | /// argume might be against a new function pointer type which creates N 24 | /// new types). 25 | static constexpr unsigned maxCreatedTypesPerRun = 3; 26 | /// Counts how many types have been created 27 | /// so far by this TypeCreator. 28 | unsigned typesCreated = 0; 29 | 30 | public: 31 | TypeCreator(MutatorData &input); 32 | 33 | /// Returns a random valid type. 34 | TypeRef getAnyType() { 35 | if (decision(Frag::PickVoidForAny)) 36 | return Void(); 37 | return getDefinedType(); 38 | } 39 | 40 | /// Returns the pointer type that points to memory of the given type. 41 | TypeRef getPtrTypeOf(TypeRef t) { 42 | return types.getOrCreateDerived(idents, Type::Kind::Pointer, t); 43 | } 44 | 45 | /// Returns a random pointer type. 46 | TypeRef getPtrType(); 47 | 48 | /// Return a random floating point type. 49 | TypeRef getAnyFloatType() { 50 | return getRng().pickOneVec(builtin.getFloatTypes()); 51 | } 52 | 53 | /// Returns a random integer type. Might be cv-qualified. 54 | /// \param allowConst Whether the type can be const-qualified. 55 | TypeRef getAnyIntType(bool allowConst = false); 56 | 57 | /// Returns a random integer or float type. 58 | TypeRef getAnyIntOrFloatType() { 59 | if (decision(Frag::PickFloatOverInt)) 60 | return getAnyFloatType(); 61 | return getAnyIntType(); 62 | } 63 | 64 | /// Returns a random integer or pointer type. 65 | TypeRef getAnyIntOrPtrType() { 66 | if (decision(Frag::PickPtrOverInt)) 67 | return getPtrType(); 68 | return getAnyIntType(); 69 | } 70 | 71 | /// Returns a random int/float/pointer type. 72 | TypeRef getAnyIntOrPtrOrFloatType() { 73 | if (decision(Frag::PickPtrOverInt)) 74 | return getPtrType(); 75 | if (decision(Frag::PickFloatOverInt)) 76 | return getAnyFloatType(); 77 | return getAnyIntType(); 78 | } 79 | 80 | /// Returns an existing type that is defined (e.g., has a size). 81 | TypeRef getExistingDefinedType() { 82 | std::vector options; 83 | for (const Type &t : types) 84 | if (!builtin.isVoid(t.getRef())) 85 | options.push_back(t.getRef()); 86 | return options.at(getRng().pickIndex(options.size())); 87 | } 88 | 89 | /// Returns an existing type that is defined and not an array type. 90 | TypeRef getExistingNonArrayDefinedType() { 91 | std::vector options; 92 | for (const Type &t : types) 93 | if (!builtin.isVoid(t.getRef()) && t.getKind() != Type::Kind::Array) 94 | options.push_back(t.getRef()); 95 | return options.at(getRng().pickIndex(options.size())); 96 | } 97 | 98 | /// Creates a new Ptr type. 99 | TypeRef makeNewPtrType(); 100 | 101 | /// Create a new function pointer type. 102 | TypeRef makeNewFuncPtrType() { 103 | return makeNewFuncPtrTypeWithResult(getReturnType()); 104 | } 105 | 106 | /// Returns a new function pointer with the given return type. 107 | TypeRef makeNewFuncPtrTypeWithResult(TypeRef ret) { 108 | auto verify = p.queueVerify(); 109 | std::vector args; 110 | for (unsigned i = 0; i < getRng().getBelow(5U); ++i) 111 | args.push_back(getExistingDefinedType()); 112 | return types.addType( 113 | Type::FunctionPointer(ret, args, idents.makeNewID("funcPtrT"))); 114 | } 115 | 116 | /// Returns a new random array type. 117 | TypeRef makeNewArrayType() { 118 | while (true) { 119 | TypeRef ret = getDefinedType(); 120 | // Avoid multi-dimensional arrays as they have weird semantics. 121 | if (types.get(ret).isArray()) 122 | continue; 123 | unsigned size = 1U + getRng().getBelow(128U); 124 | return types.addType(Type::Array(ret, size, idents.makeNewID("arrayT"))); 125 | } 126 | } 127 | 128 | /// Returns an arbtirary newly created type. 129 | TypeRef makeNewType(); 130 | 131 | /// Returns a type that is valid for a Function return type. 132 | TypeRef getReturnType() { 133 | while (true) { 134 | TypeRef t = getAnyType(); 135 | if (!isValidReturnType(t)) 136 | continue; 137 | return t; 138 | } 139 | } 140 | 141 | /// Returns a type that is defined and not const. 142 | TypeRef getDefinedNonConstType() { 143 | while (true) { 144 | TypeRef t = getDefinedType(); 145 | if (types.isConst(t)) 146 | continue; 147 | return t; 148 | } 149 | } 150 | 151 | /// Returns a type that is complete/defined. 152 | TypeRef getDefinedType(); 153 | 154 | /// Returns whatever type is used for boolean results in the current language. 155 | TypeRef getBoolType() { return builtin.signed_int; } 156 | 157 | /// Creates a random field of the given type. 158 | Record::Field makeField(TypeRef t = Void()) { 159 | if (t == Void()) 160 | t = getDefinedNonConstType(); 161 | unsigned bitsize = 0; 162 | // if (p.getBuiltin().isIntType(t)) 163 | // bitsize = getRng().getBelow(types.getBitSize(t)); 164 | Record::Field field(newID("field"), t); 165 | return field; 166 | } 167 | 168 | /// Creates a new record that contains at least one member of the given type. 169 | Record *makeRecord(TypeRef expectedMember) { 170 | auto verify = p.queueVerify(); 171 | 172 | if (expectedMember == Void()) 173 | expectedMember = getAnyIntOrFloatType(); 174 | 175 | auto recLimit = recordRecursionLimit.scope(); 176 | unsigned fieldLimit = 10; 177 | if (recLimit.reached()) 178 | fieldLimit = 0; 179 | 180 | auto r = Record::Struct(p, newID("record"), {}); 181 | for (unsigned i = 0; i < getRng().getBelow(fieldLimit); ++i) 182 | r->addField(makeField()); 183 | r->addField(makeField(expectedMember)); 184 | for (unsigned i = 0; i < getRng().getBelow(fieldLimit); ++i) 185 | r->addField(makeField()); 186 | return &p.add(std::move(r)); 187 | } 188 | 189 | /// Returns a random record type. 190 | TypeRef makeRecordType() { 191 | Record *r = makeRecord(Void()); 192 | return r->getType(); 193 | } 194 | }; 195 | 196 | #endif // TYPECREATOR_H 197 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/UnsafeDecisions.def: -------------------------------------------------------------------------------- 1 | // Set of specific mutation decisions. 2 | 3 | DECISION(GenerateLoop) 4 | DECISION(PickVoidForAny) 5 | DECISION(VarIsStatic) 6 | DECISION(InitGlobal) 7 | DECISION(SwitchLinkageGlobalVar) 8 | DECISION(PickExistingGlobal) 9 | DECISION(PickLocalVar) 10 | DECISION(EnsureReturnInFunc) 11 | DECISION(CleanupCompound) 12 | DECISION(UseSnippet) 13 | DECISION(UseMutatedStmtAsChild) 14 | DECISION(DeleteCompoundStmts) 15 | DECISION(DeleteStmtInCompound) 16 | DECISION(MutateFoundStatement) 17 | DECISION(MutateOverDelete) 18 | DECISION(ReorderOverDelete) 19 | DECISION(FunctionIsStatic) 20 | DECISION(FunctionIsNoExcept) 21 | DECISION(SwapDefAndDecl) 22 | DECISION(NewFuncBodies) 23 | DECISION(MutateFuncAttrs) 24 | DECISION(InitWithFuncAttrs) 25 | DECISION(DeleteFuncAttrs) 26 | DECISION(EmitStringLiteral) 27 | DECISION(EmitEmptyStringLiteral) 28 | DECISION(CreateNewType) 29 | DECISION(PickPtrOverInt) 30 | DECISION(PickFloatOverInt) 31 | DECISION(TryDerefVar) 32 | DECISION(VolatileInt) 33 | DECISION(ConstInt) 34 | DECISION(UseNonStdCallingConv) 35 | DECISION(UseFunctionAttr) 36 | DECISION(UseSecondFunctionAttr) 37 | DECISION(CreateFuncPtrType) 38 | DECISION(DeleteTypes) 39 | DECISION(MutateTypes) 40 | DECISION(MutateTypeBase) 41 | DECISION(MutateTypeArraySize) 42 | DECISION(RegenerateFunctionBody) 43 | DECISION(SimplifyStmt) 44 | DECISION(PreferModifyingStmtsOverExprs) 45 | DECISION(DontFillArrayConstant) 46 | DECISION(EmptyCompound) 47 | DECISION(PromoteExprChild) 48 | DECISION(ForceCallBuiltinStmt) 49 | DECISION(CallBuiltin) 50 | DECISION(CallFuncPtr) 51 | DECISION(CatchAll) 52 | DECISION(MutateCompound) 53 | DECISION(InjectMultipleStmtCompound) 54 | DECISION(PromoteChild) 55 | DECISION(PromoteChildren) 56 | DECISION(WrapInCompound) 57 | DECISION(AssignExprToVar) 58 | DECISION(GarbageCollectTypes) 59 | DECISION(FixMainReturn) 60 | DECISION(ChangeIdentifier) 61 | DECISION(MutateFunction) 62 | DECISION(MutateGlobal) 63 | DECISION(MutateRecord) 64 | DECISION(InlineCall) 65 | DECISION(OutlineExpr) 66 | DECISION(RegenerateProgram) 67 | #undef DECISION 68 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/UnsafeGenerator.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "UnsafeStrategy.h" 4 | #include "scc/program/Program.h" 5 | #include "scc/utils/Error.h" 6 | 7 | /// Program generator/mutate class that works on 'unsafe' (=buggy) programs. 8 | class UnsafeGenerator { 9 | public: 10 | typedef UnsafeStrategy Strategy; 11 | 12 | /// Returns a new simple program. 13 | std::unique_ptr generate(RngSource source, 14 | LangOpts opts = LangOpts()); 15 | 16 | /// Mutates the given program with decisions decided by the given strategy. 17 | /// 18 | /// scaleMul can be an integer > 0 that decides if the mutation process 19 | /// should be repeated multiple times. 20 | std::vector mutate(Program &p, RngSource source, 21 | const Strategy &strat, unsigned scaleMul); 22 | 23 | /// Performs a single reduction step on the given program. 24 | std::vector reduce(Program &p, RngSource source, 25 | const Strategy &strat); 26 | 27 | /// Returns a string that is prepended to the printed program code. 28 | static std::string getProgramPrefix(const Program &p); 29 | /// Returns a string that is appended to the printed program code. 30 | static std::string getProgramSuffix(const Program &p); 31 | 32 | /// Hook that allows modifying the oracle command passed from the user. 33 | /// @param exePath The path to the current fuzzer binary. 34 | /// @param cmd The command line specified by the user. 35 | /// @param seed The seed that should be used to seed Rngs. 36 | static std::string expandEvalCommand(std::string exePath, std::string cmd, 37 | RngSource source) { 38 | return cmd; 39 | } 40 | 41 | /// Returns a random program from the provided entrophy. 42 | std::unique_ptr generateFromEntrophy(EntrophyVec entrophy, 43 | UnsafeStrategy strat, 44 | LangOpts opts = LangOpts()); 45 | 46 | /// Handle custom command line arguments. 47 | OptError handleArgs(std::vector arg) { return {}; } 48 | }; 49 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/UnsafeMutatorBase.h: -------------------------------------------------------------------------------- 1 | #ifndef UNSAFEMUTATORBASE_H 2 | #define UNSAFEMUTATORBASE_H 3 | 4 | #include "StatementContext.h" 5 | #include "UnsafeStrategy.h" 6 | #include "scc/mutator-utils/MutatorBase.h" 7 | #include "scc/mutator-utils/Rng.h" 8 | #include "scc/mutator-utils/StrategyInstance.h" 9 | #include "scc/program/Statement.h" 10 | 11 | typedef StrategyInstance UnsafeInstance; 12 | 13 | /// Base class for different parts of the 'unsafe' way to mutate programs. 14 | struct UnsafeMutatorBase : MutatorBase { 15 | UnsafeMutatorBase(MutatorData &i) : MutatorBase(i) {} 16 | 17 | /// Returns true if the given var decl is references anywhere in the given 18 | /// parent statement. 19 | bool isVarUsed(const Statement &parent, const Statement &varDecl) { 20 | return parent.forAllChildren([&varDecl](const Statement &other) { 21 | return other == Stmt::Kind::LocalVarRef && 22 | other.getReferencedVarID() == varDecl.getDeclaredVarID(); 23 | }); 24 | } 25 | 26 | /// Returns true if we can directly modify the given statement. 27 | bool canMutate(const StatementContext &context, const Statement &parent, 28 | const Statement &s) { 29 | // Don't remove catch statements. They need to be 30 | // removed via removing/changing the try otherwise we end up 31 | // with weird code like this: 32 | // try {} 33 | // /*removed catch=*/; 34 | // catch (...) {} 35 | if (parent.getKind() == Statement::Kind::Try && 36 | s.getKind() == Statement::Kind::Catch) 37 | return false; 38 | 39 | if (s.getKind() == Statement::Kind::GotoLabel) { 40 | // For GotoLabels, check all goto's to make sure it is not leaving 41 | // them without a valid target. 42 | if (!context.getFuncBody().forAllChildren([&context](const Statement &s) { 43 | if (s.getKind() != Statement::Kind::GotoLabel) 44 | return true; 45 | return !context.getFuncBody().usesID(s.getJumpTarget()); 46 | })) 47 | return false; 48 | } 49 | 50 | // Array constants have weird language rules where they can happen, so 51 | // avoid messing with them directly 52 | if (s.getKind() == Statement::Kind::ArrayConstant) 53 | return false; 54 | 55 | return true; 56 | } 57 | 58 | /// Potentially assigns an expression to a variable. 59 | /// 60 | /// This is so that we make it more likely for the result to not just be 61 | /// optimized away because it might be used by code later on. 62 | Statement wrapExprInStmt(Statement c) { 63 | SCCAssert(c.isExpr(), "Already a statement?"); 64 | if (c.getEvalType() != Void() && decision(Frag::AssignExprToVar)) 65 | return Statement::VarDef(c.getEvalType(), newID(), c); 66 | return Statement::StmtExpr(c); 67 | } 68 | }; 69 | 70 | #endif // UNSAFEMUTATORBASE_H 71 | -------------------------------------------------------------------------------- /mutator/include/LookUB/mutator/UnsafeStrategy.h: -------------------------------------------------------------------------------- 1 | #ifndef UNSAFE_STRATEGY_H 2 | #define UNSAFE_STRATEGY_H 3 | 4 | #include "scc/mutator-utils/StrategyBase.h" 5 | #include 6 | 7 | enum class UnsafeFrag { 8 | #define DECISION(ID) ID, 9 | #include "UnsafeDecisions.def" 10 | }; 11 | 12 | /// Describes how likely certain mutations should be done. 13 | /// 14 | /// 'Unsafe' because the mutations of this strategy probably introduce bugs 15 | /// into programs. 16 | struct UnsafeStrategy : StrategyBase { 17 | typedef UnsafeFrag Frag; 18 | /// A fragment of our strategy. Essentially a single 19 | /// decision made by the mutator mapped to how likely 20 | /// it is to be taken. 21 | static constexpr float defaultVal = 0.5f; 22 | UnsafeStrategy(std::string name = "default") { 23 | #define DECISION(ID) values.push_back(defaultVal); 24 | #include "UnsafeDecisions.def" 25 | this->name = name; 26 | } 27 | 28 | static std::vector getAllFragments() { 29 | return { 30 | #define DECISION(ID) Frag::ID, 31 | #include "UnsafeDecisions.def" 32 | }; 33 | } 34 | 35 | /// Returns a list of mutation strategies. 36 | static std::vector makeMutateStrategies(); 37 | 38 | static std::vector makeReductionStrategies(); 39 | 40 | /// Returns the user-readable string for a decision. 41 | std::string getFragName(Frag f) const { 42 | #define DECISION(ID) \ 43 | if (f == Frag::ID) \ 44 | return #ID; 45 | #include "UnsafeDecisions.def" 46 | return "INVALID"; 47 | } 48 | }; 49 | 50 | #endif // UNSAFE_STRATEGY_H 51 | -------------------------------------------------------------------------------- /mutator/src/Canonicalizer.cpp: -------------------------------------------------------------------------------- 1 | #include "LookUB/mutator/Canonicalizer.h" 2 | 3 | typedef Statement::Kind StmtKind; 4 | 5 | static bool hasVarDecls(const Statement &s) { 6 | SCCAssert(s.getKind() == StmtKind::Compound, "Can only scan compounds"); 7 | for (const auto &c : s.getChildren()) { 8 | if (c.getKind() == StmtKind::VarDecl) 9 | return true; 10 | if (c.getKind() == StmtKind::VarDef) 11 | return true; 12 | } 13 | return false; 14 | } 15 | 16 | std::optional Canonicalizer::canonicalize(const Statement &s) { 17 | const auto &children = s.getChildren(); 18 | switch (s.getKind()) { 19 | case StmtKind::Compound: { 20 | 21 | bool hasChanges = false; 22 | 23 | std::vector newChildren; 24 | for (const Statement &child : s) { 25 | if (child.getKind() == StmtKind::Empty) 26 | continue; 27 | 28 | auto newChild = child; 29 | if (auto canonChild = canonicalize(child)) { 30 | newChild = *canonChild; 31 | hasChanges = true; 32 | } 33 | 34 | if (newChild.getKind() == StmtKind::Compound && !hasVarDecls(s)) { 35 | for (const Statement &nestedChild : newChild) 36 | newChildren.push_back(nestedChild); 37 | hasChanges = true; 38 | } else 39 | newChildren.push_back(newChild); 40 | } 41 | 42 | if (!hasChanges) 43 | break; 44 | 45 | return Statement::CompoundStmt(newChildren); 46 | } 47 | case StmtKind::If: { 48 | auto newChild = canonicalize(children.at(1)); 49 | if (newChild) 50 | return Statement::If(children.at(0), *newChild); 51 | break; 52 | } 53 | case StmtKind::While: { 54 | auto newChild = canonicalize(children.at(1)); 55 | if (newChild) 56 | return Statement::While(children.at(0), *newChild); 57 | break; 58 | } 59 | case StmtKind::Try: { 60 | std::vector newChildren; 61 | for (const Statement &child : s) { 62 | 63 | auto newChild = child; 64 | if (auto canonChild = canonicalize(child)) 65 | newChild = *canonChild; 66 | 67 | newChildren.push_back(newChild); 68 | } 69 | Statement tryBody = newChildren.front(); 70 | newChildren.erase(newChildren.begin()); 71 | return Statement::Try(tryBody, newChildren); 72 | } 73 | case StmtKind::Catch: { 74 | auto newChild = canonicalize(children.at(0)); 75 | if (newChild) 76 | return Statement::Catch(s.getVariableType(), s.getDeclaredVarID(), 77 | *newChild); 78 | break; 79 | } 80 | default: 81 | break; 82 | } 83 | return {}; 84 | } 85 | -------------------------------------------------------------------------------- /mutator/src/CodeMoving.cpp: -------------------------------------------------------------------------------- 1 | #include "LookUB/mutator/CodeMoving.h" 2 | #include "scc/program/Function.h" 3 | 4 | UnsafeMutatorBase::Modified 5 | CodeMoving::outlineStatement(const StatementContext &ctx, Statement &s) { 6 | TypeRef returnT = Void(); 7 | if (s.isExpr()) 8 | returnT = s.getEvalType(); 9 | auto f = std::make_unique( 10 | returnT, p.getIdents().makeNewID("outlined"), std::vector{}); 11 | 12 | Statement newBody = s; 13 | if (s.isExpr()) 14 | newBody = Statement::Return(newBody); 15 | f->setBody(newBody); 16 | 17 | const bool isStmt = s.isStmt(); 18 | s = Statement::Call(returnT, f->getNameID(), {}); 19 | if (isStmt) 20 | s = Statement::StmtExpr(s); 21 | 22 | p.add(std::move(f)); 23 | 24 | return Modified::Yes; 25 | } 26 | 27 | UnsafeMutatorBase::Modified CodeMoving::inlineCall(const StatementContext &ctx, 28 | Statement &call) { 29 | if (call.getKind() != Statement::Kind::Call) 30 | return Modified::No; 31 | NameID calledFuncID = call.getCalledFuncID(); 32 | Function *f = p.getFunctionWithID(calledFuncID); 33 | if (f == nullptr || f->isExternal()) 34 | return Modified::No; 35 | // TODO: Handle variables. 36 | call = f->getBody(); 37 | return Modified::Yes; 38 | } 39 | -------------------------------------------------------------------------------- /mutator/src/FunctionMutator.cpp: -------------------------------------------------------------------------------- 1 | #include "LookUB/mutator/FunctionMutator.h" 2 | -------------------------------------------------------------------------------- /mutator/src/LiteralMaker.cpp: -------------------------------------------------------------------------------- 1 | #include "LookUB/mutator/LiteralMaker.h" 2 | 3 | #include "scc/utils/Counter.h" 4 | 5 | void LiteralMaker::setupIntegers() { 6 | specialIntegers.push_back("0"); 7 | 8 | unsigned long specialUInt = 1; 9 | for (int i = 0; i <= 64; ++i) { 10 | specialIntegers.push_back(std::to_string(specialUInt) + "ULL"); 11 | specialIntegers.push_back(std::to_string(specialUInt + 1u) + "ULL"); 12 | specialIntegers.push_back(std::to_string(specialUInt - 1U) + "ULL"); 13 | specialUInt *= 2; 14 | } 15 | 16 | long specialInt = -1; 17 | for (int i = 0; i <= 62; ++i) { 18 | specialIntegers.push_back("(" + std::to_string(specialInt) + "LL)"); 19 | specialIntegers.push_back("(" + std::to_string(specialInt + 1) + "LL)"); 20 | specialIntegers.push_back("(" + std::to_string(specialInt - 1) + "LL)"); 21 | specialInt *= 2; 22 | } 23 | } 24 | 25 | void LiteralMaker::setupFloats() { 26 | specialFloats.push_back("0.0"); 27 | unsigned long specialUInt = 1; 28 | for (int i = 0; i <= 64; ++i) { 29 | specialFloats.push_back(std::to_string(specialUInt) + ".0"); 30 | specialFloats.push_back(std::to_string(specialUInt + 1u) + ".0"); 31 | specialFloats.push_back(std::to_string(specialUInt - 1U) + ".0"); 32 | specialUInt *= 2; 33 | } 34 | } 35 | 36 | LiteralMaker::LiteralMaker(MutatorData &input) : UnsafeMutatorBase(input) { 37 | setupIntegers(); 38 | setupFloats(); 39 | } 40 | 41 | Statement LiteralMaker::makeConstant(const StatementContext &, TypeRef t) { 42 | auto verify = p.queueVerify(); 43 | if (getType(t).getKind() == Type::Kind::Array) { 44 | auto verify = p.queueVerify(); 45 | t = types.getOrCreateDerived(idents, Type::Kind::Pointer, 46 | getType(t).getBase()); 47 | } 48 | const std::string str = makeConstantStr(t); 49 | return Statement::Cast(t, Statement::Constant(str, t)); 50 | } 51 | 52 | std::string LiteralMaker::makeConstantStr(TypeRef tref) { 53 | tref = stripCV(tref); 54 | 55 | SCCAssertNotEqual(tref, builtin.void_type, 56 | "Trying to make void str literal?"); 57 | if (builtin.isIntType(tref)) 58 | return getRng().pickOneVec(specialIntegers); 59 | if (builtin.isFloatType(tref)) 60 | return getRng().pickOneVec(specialFloats); 61 | 62 | Type t = getType(tref); 63 | if (t.getKind() == Type::Kind::Pointer) { 64 | if (tref == builtin.const_char_ptr && decision(Frag::EmitStringLiteral)) { 65 | std::string res = "\""; 66 | if (!decision(Frag::EmitEmptyStringLiteral)) 67 | for (unsigned int i = 0; i < getRng().getBelow(10); ++i) 68 | getRng().pickOneStr("abcdefghZSDF0123456789$&^()"); 69 | res += "\""; 70 | return res; 71 | } 72 | 73 | return getRng().pickOne({"0", "-1", "1"}); 74 | } 75 | 76 | if (t.getKind() == Type::Kind::FunctionPointer) 77 | return getRng().pickOne({"0", "-1"}); 78 | 79 | if (t.getKind() == Type::Kind::Array) { 80 | return getRng().pickOne({"{0}", "{1, 2}", "{}", "{1}"}); 81 | } 82 | 83 | if (t.getKind() == Type::Kind::Record) 84 | return getRng().pickOne({"{0}", "{}"}); 85 | 86 | if (t.getKind() == Type::Kind::Invalid) { 87 | SCCError("Requesting constant string of invalid type?"); 88 | return "INVALID_TYPE_REQUESTED"; 89 | } 90 | 91 | SCCError( 92 | "Unimplemented type: " + std::to_string(static_cast(t.getKind())) + 93 | " (id: " + std::to_string(static_cast(tref.getInternalVal())) + ")"); 94 | return "ERR"; 95 | } 96 | -------------------------------------------------------------------------------- /mutator/src/Simplifier.cpp: -------------------------------------------------------------------------------- 1 | #include "LookUB/mutator/Simplifier.h" 2 | 3 | Simplifier::Simplifier(MutatorData &input) 4 | : UnsafeMutatorBase(input), literals(input) {} 5 | 6 | bool Simplifier::simplifyStmt(StatementContext context, const Statement &parent, 7 | Statement &s) { 8 | if (!canMutate(context, parent, s)) 9 | return false; 10 | if (s.getKind() == StmtKind::Compound && decision(Frag::EmptyCompound)) { 11 | s = Statement::CompoundStmt({}); 12 | p.verifySelf(); 13 | return true; 14 | } 15 | 16 | if (simplifyCompound(s)) 17 | return true; 18 | 19 | if (s.getEvalType() == Void()) { 20 | s = Statement::Empty(); 21 | p.verifySelf(); 22 | return true; 23 | } 24 | s = literals.makeConstant(context, s.getEvalType()); 25 | return true; 26 | } 27 | 28 | bool Simplifier::simplifyCompound(Statement &s) { 29 | if (s.getKind() != StmtKind::Compound) 30 | return false; 31 | if (decision(Frag::CleanupCompound)) { 32 | std::vector cleanChildren; 33 | for (const Statement &c : s) { 34 | if (!c.isEmpty()) 35 | cleanChildren.push_back(c); 36 | } 37 | if (cleanChildren.size() == s.getNumChildren()) 38 | return false; 39 | s = Statement::CompoundStmt(cleanChildren); 40 | p.verifySelf(); 41 | return true; 42 | } 43 | if (!decision(Frag::DeleteCompoundStmts)) 44 | return false; 45 | 46 | std::vector cleanChildren; 47 | for (const Statement &c : s) 48 | if (!decision(Frag::DeleteStmtInCompound)) 49 | cleanChildren.push_back(c); 50 | if (cleanChildren.empty()) 51 | cleanChildren = {Statement::Empty()}; 52 | 53 | if (cleanChildren.size() == s.getNumChildren()) 54 | return false; 55 | s = Statement::CompoundStmt(cleanChildren); 56 | p.verifySelf(); 57 | return true; 58 | } 59 | -------------------------------------------------------------------------------- /mutator/src/Snippets.cpp: -------------------------------------------------------------------------------- 1 | #include "LookUB/mutator/Snippets.h" 2 | 3 | using Stmt = Statement; 4 | using Kind = Stmt::Kind; 5 | 6 | static Statement binOp(Program &p, Kind op, Statement l, Statement r) { 7 | return Stmt::BinaryOp(p, op, l, r); 8 | } 9 | 10 | Statement Snippets::createSnippetImpl(const StatementContext &ctx) { 11 | constexpr auto Compound = Stmt::CompoundStmt; 12 | constexpr auto VarDef = Stmt::VarDef; 13 | constexpr auto VarDecl = Stmt::VarDecl; 14 | constexpr auto While = Stmt::While; 15 | constexpr auto StmtExpr = Stmt::StmtExpr; 16 | constexpr auto Empty = Stmt::Empty; 17 | constexpr auto CommentStmt = Stmt::CommentStmt; 18 | constexpr auto Goto = Stmt::Goto; 19 | constexpr auto Break = Stmt::Break; 20 | constexpr auto GotoLabel = Stmt::GotoLabel; 21 | constexpr auto LocalVarRef = Stmt::LocalVarRef; 22 | constexpr auto Constant = Stmt::Constant; 23 | constexpr auto Subscript = Stmt::Subscript; 24 | 25 | using BI = BuiltinFunctions::Kind; 26 | auto getBuiltin = [this](BI b) { 27 | Function *f = builtinFuncs.get(p, b); 28 | SCCAssert(f, "Failed to create builtin?"); 29 | return f; 30 | }; 31 | 32 | enum Option { 33 | ForwardJump, 34 | BackwardsJump, 35 | MallocFree, 36 | CounterLoop, 37 | InfLoop, 38 | NoLoop, 39 | ArrayWithUse, 40 | UseAfterReturn, 41 | }; 42 | 43 | std::vector