|
| 1 | +#! /usr/bin/env python |
| 2 | + |
| 3 | +# Copyright 2016 WebAssembly Community Group participants |
| 4 | +# |
| 5 | +# Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | +# you may not use this file except in compliance with the License. |
| 7 | +# You may obtain a copy of the License at |
| 8 | +# |
| 9 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | +# |
| 11 | +# Unless required by applicable law or agreed to in writing, software |
| 12 | +# distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | +# See the License for the specific language governing permissions and |
| 15 | +# limitations under the License. |
| 16 | + |
| 17 | +''' |
| 18 | +This fuzzes the relooper using the C API. |
| 19 | +''' |
| 20 | + |
| 21 | +import difflib |
| 22 | +import os |
| 23 | +import random |
| 24 | +import subprocess |
| 25 | + |
| 26 | +if os.environ.get('LD_LIBRARY_PATH'): |
| 27 | + os.environ['LD_LIBRARY_PATH'] += os.pathsep + 'lib' |
| 28 | +else: |
| 29 | + os.environ['LD_LIBRARY_PATH'] = 'lib' |
| 30 | + |
| 31 | +while True: |
| 32 | + # Random decisions |
| 33 | + num = random.randint(2, 100) # TODO: 250+ |
| 34 | + density = random.random() * random.random() |
| 35 | + decisions = [random.randint(1, num * 20) for x in range(num * 3)] |
| 36 | + branches = [0] * num |
| 37 | + defaults = [0] * num |
| 38 | + for i in range(num): |
| 39 | + b = set([]) |
| 40 | + bs = random.randint(1, max(1, |
| 41 | + round(density * random.random() * (num - 1)))) |
| 42 | + for j in range(bs): |
| 43 | + b.add(random.randint(1, num - 1)) |
| 44 | + b = list(b) |
| 45 | + defaults[i] = random.choice(b) |
| 46 | + b.remove(defaults[i]) |
| 47 | + branches[i] = b |
| 48 | + optimize = random.random() < 0.5 |
| 49 | + print num, density, optimize |
| 50 | + |
| 51 | + for temp in ['fuzz.wasm', 'fuzz.wast', 'fast.txt', 'fuzz.slow.js', |
| 52 | + 'fuzz.cpp']: |
| 53 | + try: |
| 54 | + os.unlink(temp) |
| 55 | + except: |
| 56 | + pass |
| 57 | + |
| 58 | + # parts |
| 59 | + entry = ''' |
| 60 | +var label = 0; |
| 61 | +var state; |
| 62 | +var decisions = %s; |
| 63 | +var index = 0; |
| 64 | +function check() { |
| 65 | + if (index == decisions.length) throw 'HALT'; |
| 66 | + console.log('(i32.const ' + (-decisions[index]) + ')'); |
| 67 | + return decisions[index++]; |
| 68 | +} |
| 69 | +''' % str(decisions) |
| 70 | + |
| 71 | + slow = entry + '\n' |
| 72 | + slow += 'label = 0;\n' |
| 73 | + |
| 74 | + slow += ''' |
| 75 | +while(1) switch(label) { |
| 76 | +''' |
| 77 | + |
| 78 | + fast = ''' |
| 79 | +
|
| 80 | +#include <assert.h> |
| 81 | +#include <stdio.h> |
| 82 | +
|
| 83 | +#include "binaryen-c.h" |
| 84 | +
|
| 85 | +// globals: address 4 is index |
| 86 | +// decisions are at address 8+ |
| 87 | +
|
| 88 | +int main() { |
| 89 | + BinaryenModuleRef module = BinaryenModuleCreate(); |
| 90 | +
|
| 91 | + // check() |
| 92 | +
|
| 93 | + // if the end, halt |
| 94 | + BinaryenExpressionRef halter = BinaryenIf(module, |
| 95 | + BinaryenBinary(module, |
| 96 | + BinaryenEq(), |
| 97 | + BinaryenLoad(module, 4, 0, 0, 0, BinaryenInt32(), |
| 98 | + BinaryenConst(module, BinaryenLiteralInt32(4))), |
| 99 | + BinaryenConst(module, BinaryenLiteralInt32(4 * %d)) // jumps of 4 bytes |
| 100 | + ), |
| 101 | + BinaryenUnreachable(module), |
| 102 | + NULL |
| 103 | + ); |
| 104 | + // increment index |
| 105 | + BinaryenExpressionRef incer = BinaryenStore(module, |
| 106 | + 4, 0, 0, |
| 107 | + BinaryenConst(module, BinaryenLiteralInt32(4)), |
| 108 | + BinaryenBinary(module, |
| 109 | + BinaryenAdd(), |
| 110 | + BinaryenLoad(module, 4, 0, 0, 0, BinaryenInt32(), |
| 111 | + BinaryenConst(module, BinaryenLiteralInt32(4))), |
| 112 | + BinaryenConst(module, BinaryenLiteralInt32(4)) |
| 113 | + ) |
| 114 | + ); |
| 115 | +
|
| 116 | + // optionally, print the return value |
| 117 | + BinaryenExpressionRef args[] = { |
| 118 | + BinaryenBinary(module, |
| 119 | + BinaryenSub(), |
| 120 | + BinaryenConst(module, BinaryenLiteralInt32(0)), |
| 121 | + BinaryenLoad(module, |
| 122 | + 4, 0, 4, 0, BinaryenInt32(), |
| 123 | + BinaryenLoad(module, 4, 0, 0, 0, BinaryenInt32(), |
| 124 | + BinaryenConst(module, BinaryenLiteralInt32(4))) |
| 125 | + ) |
| 126 | + ) |
| 127 | + }; |
| 128 | + BinaryenExpressionRef debugger; |
| 129 | + if (1) debugger = BinaryenCallImport(module, "print", args, 1, |
| 130 | + BinaryenNone()); |
| 131 | + else debugger = BinaryenNop(module); |
| 132 | +
|
| 133 | + // return the decision. need to subtract 4 that we just added, |
| 134 | + // and add 8 since that's where we start, so overall offset 4 |
| 135 | + BinaryenExpressionRef returner = BinaryenLoad(module, |
| 136 | + 4, 0, 4, 0, BinaryenInt32(), |
| 137 | + BinaryenLoad(module, 4, 0, 0, 0, BinaryenInt32(), |
| 138 | + BinaryenConst(module, BinaryenLiteralInt32(4))) |
| 139 | + ); |
| 140 | + BinaryenExpressionRef checkBodyList[] = { halter, incer, debugger, |
| 141 | + returner }; |
| 142 | + BinaryenExpressionRef checkBody = BinaryenBlock(module, |
| 143 | + NULL, checkBodyList, sizeof(checkBodyList) / sizeof(BinaryenExpressionRef) |
| 144 | + ); |
| 145 | + BinaryenFunctionTypeRef i = BinaryenAddFunctionType(module, "i", |
| 146 | + BinaryenInt32(), |
| 147 | + NULL, 0); |
| 148 | + BinaryenAddFunction(module, "check", i, NULL, 0, checkBody); |
| 149 | +
|
| 150 | + // contents of main() begin here |
| 151 | +
|
| 152 | + RelooperRef relooper = RelooperCreate(); |
| 153 | +
|
| 154 | +''' % len(decisions) |
| 155 | + |
| 156 | + for i in range(0, num): |
| 157 | + slow += ' case %d: console.log("(i32.const %d)"); state = check(); \n' % ( |
| 158 | + i, i) |
| 159 | + b = branches[i] |
| 160 | + for j in range(len(b)): |
| 161 | + slow += ' if (state %% %d == %d) { label = %d; break }\n' % ( |
| 162 | + len(b) + 1, j, b[j]) # TODO: split range 1-n into these options |
| 163 | + slow += ' label = %d; break\n' % defaults[i] |
| 164 | + |
| 165 | + for i in range(num): |
| 166 | + fast += ''' |
| 167 | + RelooperBlockRef b%d; |
| 168 | + { |
| 169 | + BinaryenExpressionRef args[] = { |
| 170 | + BinaryenConst(module, BinaryenLiteralInt32(%d)) |
| 171 | + }; |
| 172 | + BinaryenExpressionRef list[] = { |
| 173 | + BinaryenCallImport(module, "print", args, 1, BinaryenNone()), |
| 174 | + BinaryenSetLocal(module, 0, BinaryenCall(module, "check", NULL, 0, |
| 175 | + BinaryenInt32())) |
| 176 | + }; |
| 177 | + b%d = RelooperAddBlock(relooper, BinaryenBlock(module, NULL, list, 2)); |
| 178 | + } |
| 179 | +''' % (i, i, i) |
| 180 | + |
| 181 | + for i in range(num): |
| 182 | + b = branches[i] |
| 183 | + for j in range(len(b)): |
| 184 | + fast += ''' |
| 185 | + RelooperAddBranch(b%d, b%d, BinaryenBinary(module, |
| 186 | + BinaryenEq(), |
| 187 | + BinaryenBinary(module, |
| 188 | + BinaryenRemU(), |
| 189 | + BinaryenGetLocal(module, 0, BinaryenInt32()), |
| 190 | + BinaryenConst(module, BinaryenLiteralInt32(%d)) |
| 191 | + ), |
| 192 | + BinaryenConst(module, BinaryenLiteralInt32(%d)) |
| 193 | + ), NULL); |
| 194 | +''' % (i, b[j], len(b) + 1, j) |
| 195 | + fast += ''' |
| 196 | + RelooperAddBranch(b%d, b%d, NULL, NULL); |
| 197 | +''' % (i, defaults[i]) |
| 198 | + |
| 199 | + fast += ''' |
| 200 | + BinaryenExpressionRef body = RelooperRenderAndDispose(relooper, b0, 1, |
| 201 | + module); |
| 202 | +
|
| 203 | + int decisions[] = { %s }; |
| 204 | + int numDecisions = sizeof(decisions)/sizeof(int); |
| 205 | +
|
| 206 | + // write out all the decisions, then the body of the function |
| 207 | + BinaryenExpressionRef full[numDecisions + 1]; |
| 208 | +
|
| 209 | + for (int i = 0; i < numDecisions; i++) { |
| 210 | + full[i] = BinaryenStore(module, |
| 211 | + 4, 0, 0, |
| 212 | + BinaryenConst(module, BinaryenLiteralInt32(8 + 4 * i)), |
| 213 | + BinaryenConst(module, BinaryenLiteralInt32(decisions[i])) |
| 214 | + ); |
| 215 | + } |
| 216 | + full[numDecisions] = body; |
| 217 | + BinaryenExpressionRef all = BinaryenBlock(module, NULL, full, |
| 218 | + numDecisions + 1); |
| 219 | +
|
| 220 | + BinaryenFunctionTypeRef v = BinaryenAddFunctionType(module, "v", |
| 221 | + BinaryenNone(), |
| 222 | + NULL, 0); |
| 223 | + // locals: state, free-for-label |
| 224 | + BinaryenType localTypes[] = { BinaryenInt32(), BinaryenInt32() }; |
| 225 | + BinaryenFunctionRef theMain = BinaryenAddFunction(module, "main", v, |
| 226 | + localTypes, 2, all); |
| 227 | + BinaryenSetStart(module, theMain); |
| 228 | +
|
| 229 | + // import |
| 230 | +
|
| 231 | + BinaryenType iparams[] = { BinaryenInt32() }; |
| 232 | + BinaryenFunctionTypeRef vi = BinaryenAddFunctionType(module, "vi", |
| 233 | + BinaryenNone(), |
| 234 | + iparams, 1); |
| 235 | + BinaryenAddImport(module, "print", "spectest", "print", vi); |
| 236 | +
|
| 237 | + // memory |
| 238 | + BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, 0); |
| 239 | +
|
| 240 | + // optionally, optimize |
| 241 | + if (%d) BinaryenModuleOptimize(module); |
| 242 | +
|
| 243 | + assert(BinaryenModuleValidate(module)); |
| 244 | +
|
| 245 | + // write it out |
| 246 | +
|
| 247 | + BinaryenModulePrint(module); |
| 248 | +
|
| 249 | + BinaryenModuleDispose(module); |
| 250 | +
|
| 251 | + return 0; |
| 252 | +} |
| 253 | +''' % (', '.join(map(str, decisions)), optimize) |
| 254 | + |
| 255 | + slow += '}' |
| 256 | + |
| 257 | + open('fuzz.slow.js', 'w').write(slow) |
| 258 | + open('fuzz.cpp', 'w').write(fast) |
| 259 | + |
| 260 | + print '.' |
| 261 | + cmd = ['g++', 'fuzz.cpp', '-Isrc', '-g', '-lbinaryen-c', '-lasmjs', |
| 262 | + '-lsupport', '-Llib/.', '-pthread', '-o', 'fuzz', '-g'] |
| 263 | + subprocess.check_call(cmd) |
| 264 | + print '^' |
| 265 | + subprocess.check_call(['./fuzz'], stdout=open('fuzz.wast', 'w')) |
| 266 | + print '*' |
| 267 | + subprocess.call(['bin/binaryen-shell', 'fuzz.wast'], |
| 268 | + stdout=open('fast.txt', 'w'), stderr=subprocess.PIPE) |
| 269 | + fast_out = open('fast.txt').read() |
| 270 | + print '-' |
| 271 | + slow_out = subprocess.Popen(['nodejs', 'fuzz.slow.js'], |
| 272 | + stdout=subprocess.PIPE, |
| 273 | + stderr=subprocess.PIPE).communicate()[0] |
| 274 | + print '_' |
| 275 | + |
| 276 | + if slow_out != fast_out: |
| 277 | + print ''.join([a.rstrip() + '\n' for a in difflib.unified_diff( |
| 278 | + slow_out.split('\n'), |
| 279 | + fast_out.split('\n'), |
| 280 | + fromfile='slow', |
| 281 | + tofile='fast')]) |
| 282 | + assert False |
0 commit comments