1 /* 2 * Copyright 2016 Google Inc. 3 * 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 #include "Fuzz.h" 9 #include "SkCanvas.h" 10 #include "SkCodec.h" 11 #include "SkCommandLineFlags.h" 12 #include "SkData.h" 13 #include "SkImage.h" 14 #include "SkImageEncoder.h" 15 #include "SkImageFilter.h" 16 #include "SkMallocPixelRef.h" 17 #include "SkOSFile.h" 18 #include "SkOSPath.h" 19 #include "SkPaint.h" 20 #include "SkPath.h" 21 #include "SkPicture.h" 22 #include "SkPipe.h" 23 #include "SkReadBuffer.h" 24 #include "SkRegion.h" 25 #include "SkStream.h" 26 #include "SkSurface.h" 27 #include "SkTextBlob.h" 28 29 #if SK_SUPPORT_GPU 30 #include "SkSLCompiler.h" 31 #endif 32 33 #include <iostream> 34 #include <signal.h> 35 #include "sk_tool_utils.h" 36 37 38 DEFINE_string2(bytes, b, "", "A path to a file or a directory. If a file, the " 39 "contents will be used as the fuzz bytes. If a directory, all files " 40 "in the directory will be used as fuzz bytes for the fuzzer, one at a " 41 "time."); 42 DEFINE_string2(name, n, "", "If --type is 'api', fuzz the API with this name."); 43 DEFINE_string2(dump, d, "", "If not empty, dump 'image*' or 'skp' types as a " 44 "PNG with this name."); 45 DEFINE_bool2(verbose, v, false, "Print more information while fuzzing."); 46 DEFINE_string2(type, t, "", "How to interpret --bytes, one of:\n" 47 "api\n" 48 "color_deserialize\n" 49 "filter_fuzz (equivalent to Chrome's filter_fuzz_stub)\n" 50 "icc\n" 51 "image_mode\n" 52 "image_scale\n" 53 "path_deserialize\n" 54 "pipe\n" 55 "region_deserialize\n" 56 "region_set_path\n" 57 "skp\n" 58 "sksl2glsl\n" 59 "textblob"); 60 61 static int fuzz_file(const char* path); 62 static uint8_t calculate_option(SkData*); 63 64 static void fuzz_api(sk_sp<SkData>); 65 static void fuzz_color_deserialize(sk_sp<SkData>); 66 static void fuzz_filter_fuzz(sk_sp<SkData>); 67 static void fuzz_icc(sk_sp<SkData>); 68 static void fuzz_img(sk_sp<SkData>, uint8_t, uint8_t); 69 static void fuzz_path_deserialize(sk_sp<SkData>); 70 static void fuzz_region_deserialize(sk_sp<SkData>); 71 static void fuzz_region_set_path(sk_sp<SkData>); 72 static void fuzz_skp(sk_sp<SkData>); 73 static void fuzz_skpipe(sk_sp<SkData>); 74 static void fuzz_textblob_deserialize(sk_sp<SkData>); 75 76 #if SK_SUPPORT_GPU 77 static void fuzz_sksl2glsl(sk_sp<SkData>); 78 #endif 79 80 int main(int argc, char** argv) { 81 SkCommandLineFlags::SetUsage("Usage: fuzz -t <type> -b <path/to/file> [-n api-to-fuzz]\n" 82 "--help lists the valid types\n"); 83 SkCommandLineFlags::Parse(argc, argv); 84 85 const char* path = FLAGS_bytes.isEmpty() ? argv[0] : FLAGS_bytes[0]; 86 87 if (!sk_isdir(path)) { 88 return fuzz_file(path); 89 } 90 91 SkOSFile::Iter it(path); 92 for (SkString file; it.next(&file); ) { 93 SkString p = SkOSPath::Join(path, file.c_str()); 94 SkDebugf("Fuzzing %s\n", p.c_str()); 95 int rv = fuzz_file(p.c_str()); 96 if (rv != 0) { 97 return rv; 98 } 99 } 100 return 0; 101 } 102 103 static int fuzz_file(const char* path) { 104 sk_sp<SkData> bytes(SkData::MakeFromFileName(path)); 105 if (!bytes) { 106 SkDebugf("Could not read %s\n", path); 107 return 1; 108 } 109 110 if (!FLAGS_type.isEmpty()) { 111 if (0 == strcmp("api", FLAGS_type[0])) { 112 fuzz_api(bytes); 113 return 0; 114 } 115 if (0 == strcmp("color_deserialize", FLAGS_type[0])) { 116 fuzz_color_deserialize(bytes); 117 return 0; 118 } 119 if (0 == strcmp("icc", FLAGS_type[0])) { 120 fuzz_icc(bytes); 121 return 0; 122 } 123 if (0 == strcmp("image_scale", FLAGS_type[0])) { 124 uint8_t option = calculate_option(bytes.get()); 125 fuzz_img(bytes, option, 0); 126 return 0; 127 } 128 if (0 == strcmp("image_mode", FLAGS_type[0])) { 129 uint8_t option = calculate_option(bytes.get()); 130 fuzz_img(bytes, 0, option); 131 return 0; 132 } 133 if (0 == strcmp("path_deserialize", FLAGS_type[0])) { 134 fuzz_path_deserialize(bytes); 135 return 0; 136 } 137 if (0 == strcmp("region_deserialize", FLAGS_type[0])) { 138 fuzz_region_deserialize(bytes); 139 return 0; 140 } 141 if (0 == strcmp("region_set_path", FLAGS_type[0])) { 142 fuzz_region_set_path(bytes); 143 return 0; 144 } 145 if (0 == strcmp("pipe", FLAGS_type[0])) { 146 fuzz_skpipe(bytes); 147 return 0; 148 } 149 if (0 == strcmp("skp", FLAGS_type[0])) { 150 fuzz_skp(bytes); 151 return 0; 152 } 153 if (0 == strcmp("filter_fuzz", FLAGS_type[0])) { 154 fuzz_filter_fuzz(bytes); 155 return 0; 156 } 157 if (0 == strcmp("textblob", FLAGS_type[0])) { 158 fuzz_textblob_deserialize(bytes); 159 return 0; 160 } 161 #if SK_SUPPORT_GPU 162 if (0 == strcmp("sksl2glsl", FLAGS_type[0])) { 163 fuzz_sksl2glsl(bytes); 164 return 0; 165 } 166 #endif 167 } 168 SkCommandLineFlags::PrintUsage(); 169 return 1; 170 } 171 172 // This adds up the first 1024 bytes and returns it as an 8 bit integer. This allows afl-fuzz to 173 // deterministically excercise different paths, or *options* (such as different scaling sizes or 174 // different image modes) without needing to introduce a parameter. This way we don't need a 175 // image_scale1, image_scale2, image_scale4, etc fuzzer, we can just have a image_scale fuzzer. 176 // Clients are expected to transform this number into a different range, e.g. with modulo (%). 177 static uint8_t calculate_option(SkData* bytes) { 178 uint8_t total = 0; 179 const uint8_t* data = bytes->bytes(); 180 for (size_t i = 0; i < 1024 && i < bytes->size(); i++) { 181 total += data[i]; 182 } 183 return total; 184 } 185 186 static void fuzz_api(sk_sp<SkData> bytes) { 187 const char* name = FLAGS_name.isEmpty() ? "" : FLAGS_name[0]; 188 189 for (auto r = sk_tools::Registry<Fuzzable>::Head(); r; r = r->next()) { 190 auto fuzzable = r->factory(); 191 if (0 == strcmp(name, fuzzable.name)) { 192 SkDebugf("Fuzzing %s...\n", fuzzable.name); 193 Fuzz fuzz(std::move(bytes)); 194 fuzzable.fn(&fuzz); 195 SkDebugf("[terminated] Success!\n"); 196 return; 197 } 198 } 199 200 SkDebugf("When using --type api, please choose an API to fuzz with --name/-n:\n"); 201 for (auto r = sk_tools::Registry<Fuzzable>::Head(); r; r = r->next()) { 202 auto fuzzable = r->factory(); 203 SkDebugf("\t%s\n", fuzzable.name); 204 } 205 } 206 207 static void dump_png(SkBitmap bitmap) { 208 if (!FLAGS_dump.isEmpty()) { 209 sk_tool_utils::EncodeImageToFile(FLAGS_dump[0], bitmap, SkEncodedImageFormat::kPNG, 100); 210 SkDebugf("Dumped to %s\n", FLAGS_dump[0]); 211 } 212 } 213 214 static void fuzz_img(sk_sp<SkData> bytes, uint8_t scale, uint8_t mode) { 215 // We can scale 1x, 2x, 4x, 8x, 16x 216 scale = scale % 5; 217 float fscale = (float)pow(2.0f, scale); 218 SkDebugf("Scaling factor: %f\n", fscale); 219 220 // We have 5 different modes of decoding. 221 mode = mode % 5; 222 SkDebugf("Mode: %d\n", mode); 223 224 // This is mostly copied from DMSrcSink's CodecSrc::draw method. 225 SkDebugf("Decoding\n"); 226 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(bytes)); 227 if (nullptr == codec.get()) { 228 SkDebugf("[terminated] Couldn't create codec.\n"); 229 return; 230 } 231 232 SkImageInfo decodeInfo = codec->getInfo(); 233 SkISize size = codec->getScaledDimensions(fscale); 234 decodeInfo = decodeInfo.makeWH(size.width(), size.height()); 235 236 SkBitmap bitmap; 237 SkCodec::Options options; 238 options.fZeroInitialized = SkCodec::kYes_ZeroInitialized; 239 240 if (!bitmap.tryAllocPixelsFlags(decodeInfo, SkBitmap::kZeroPixels_AllocFlag)) { 241 SkDebugf("[terminated] Could not allocate memory. Image might be too large (%d x %d)", 242 decodeInfo.width(), decodeInfo.height()); 243 return; 244 } 245 246 switch (mode) { 247 case 0: {//kCodecZeroInit_Mode, kCodec_Mode 248 switch (codec->getPixels(decodeInfo, bitmap.getPixels(), bitmap.rowBytes(), &options)) { 249 case SkCodec::kSuccess: 250 SkDebugf("[terminated] Success!\n"); 251 break; 252 case SkCodec::kIncompleteInput: 253 SkDebugf("[terminated] Partial Success\n"); 254 break; 255 case SkCodec::kErrorInInput: 256 SkDebugf("[terminated] Partial Success with error\n"); 257 break; 258 case SkCodec::kInvalidConversion: 259 SkDebugf("Incompatible colortype conversion\n"); 260 // Crash to allow afl-fuzz to know this was a bug. 261 raise(SIGSEGV); 262 default: 263 SkDebugf("[terminated] Couldn't getPixels.\n"); 264 return; 265 } 266 break; 267 } 268 case 1: {//kScanline_Mode 269 if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo)) { 270 SkDebugf("[terminated] Could not start scanline decoder\n"); 271 return; 272 } 273 274 void* dst = bitmap.getAddr(0, 0); 275 size_t rowBytes = bitmap.rowBytes(); 276 uint32_t height = decodeInfo.height(); 277 switch (codec->getScanlineOrder()) { 278 case SkCodec::kTopDown_SkScanlineOrder: 279 case SkCodec::kBottomUp_SkScanlineOrder: 280 // We do not need to check the return value. On an incomplete 281 // image, memory will be filled with a default value. 282 codec->getScanlines(dst, height, rowBytes); 283 break; 284 } 285 SkDebugf("[terminated] Success!\n"); 286 break; 287 } 288 case 2: { //kStripe_Mode 289 const int height = decodeInfo.height(); 290 // This value is chosen arbitrarily. We exercise more cases by choosing a value that 291 // does not align with image blocks. 292 const int stripeHeight = 37; 293 const int numStripes = (height + stripeHeight - 1) / stripeHeight; 294 295 // Decode odd stripes 296 if (SkCodec::kSuccess != codec->startScanlineDecode(decodeInfo) 297 || SkCodec::kTopDown_SkScanlineOrder != codec->getScanlineOrder()) { 298 // This mode was designed to test the new skip scanlines API in libjpeg-turbo. 299 // Jpegs have kTopDown_SkScanlineOrder, and at this time, it is not interesting 300 // to run this test for image types that do not have this scanline ordering. 301 SkDebugf("[terminated] Could not start top-down scanline decoder\n"); 302 return; 303 } 304 305 for (int i = 0; i < numStripes; i += 2) { 306 // Skip a stripe 307 const int linesToSkip = SkTMin(stripeHeight, height - i * stripeHeight); 308 codec->skipScanlines(linesToSkip); 309 310 // Read a stripe 311 const int startY = (i + 1) * stripeHeight; 312 const int linesToRead = SkTMin(stripeHeight, height - startY); 313 if (linesToRead > 0) { 314 codec->getScanlines(bitmap.getAddr(0, startY), linesToRead, bitmap.rowBytes()); 315 } 316 } 317 318 // Decode even stripes 319 const SkCodec::Result startResult = codec->startScanlineDecode(decodeInfo); 320 if (SkCodec::kSuccess != startResult) { 321 SkDebugf("[terminated] Failed to restart scanline decoder with same parameters.\n"); 322 return; 323 } 324 for (int i = 0; i < numStripes; i += 2) { 325 // Read a stripe 326 const int startY = i * stripeHeight; 327 const int linesToRead = SkTMin(stripeHeight, height - startY); 328 codec->getScanlines(bitmap.getAddr(0, startY), linesToRead, bitmap.rowBytes()); 329 330 // Skip a stripe 331 const int linesToSkip = SkTMin(stripeHeight, height - (i + 1) * stripeHeight); 332 if (linesToSkip > 0) { 333 codec->skipScanlines(linesToSkip); 334 } 335 } 336 SkDebugf("[terminated] Success!\n"); 337 break; 338 } 339 case 3: { //kSubset_Mode 340 // Arbitrarily choose a divisor. 341 int divisor = 2; 342 // Total width/height of the image. 343 const int W = codec->getInfo().width(); 344 const int H = codec->getInfo().height(); 345 if (divisor > W || divisor > H) { 346 SkDebugf("[terminated] Cannot codec subset: divisor %d is too big " 347 "with dimensions (%d x %d)\n", divisor, W, H); 348 return; 349 } 350 // subset dimensions 351 // SkWebpCodec, the only one that supports subsets, requires even top/left boundaries. 352 const int w = SkAlign2(W / divisor); 353 const int h = SkAlign2(H / divisor); 354 SkIRect subset; 355 SkCodec::Options opts; 356 opts.fSubset = ⊂ 357 SkBitmap subsetBm; 358 // We will reuse pixel memory from bitmap. 359 void* pixels = bitmap.getPixels(); 360 // Keep track of left and top (for drawing subsetBm into canvas). We could use 361 // fscale * x and fscale * y, but we want integers such that the next subset will start 362 // where the last one ended. So we'll add decodeInfo.width() and height(). 363 int left = 0; 364 for (int x = 0; x < W; x += w) { 365 int top = 0; 366 for (int y = 0; y < H; y+= h) { 367 // Do not make the subset go off the edge of the image. 368 const int preScaleW = SkTMin(w, W - x); 369 const int preScaleH = SkTMin(h, H - y); 370 subset.setXYWH(x, y, preScaleW, preScaleH); 371 // And fscale 372 // FIXME: Should we have a version of getScaledDimensions that takes a subset 373 // into account? 374 decodeInfo = decodeInfo.makeWH( 375 SkTMax(1, SkScalarRoundToInt(preScaleW * fscale)), 376 SkTMax(1, SkScalarRoundToInt(preScaleH * fscale))); 377 size_t rowBytes = decodeInfo.minRowBytes(); 378 if (!subsetBm.installPixels(decodeInfo, pixels, rowBytes)) { 379 SkDebugf("[terminated] Could not install pixels.\n"); 380 return; 381 } 382 const SkCodec::Result result = codec->getPixels(decodeInfo, pixels, rowBytes, 383 &opts); 384 switch (result) { 385 case SkCodec::kSuccess: 386 case SkCodec::kIncompleteInput: 387 case SkCodec::kErrorInInput: 388 SkDebugf("okay\n"); 389 break; 390 case SkCodec::kInvalidConversion: 391 if (0 == (x|y)) { 392 // First subset is okay to return unimplemented. 393 SkDebugf("[terminated] Incompatible colortype conversion\n"); 394 return; 395 } 396 // If the first subset succeeded, a later one should not fail. 397 // fall through to failure 398 case SkCodec::kUnimplemented: 399 if (0 == (x|y)) { 400 // First subset is okay to return unimplemented. 401 SkDebugf("[terminated] subset codec not supported\n"); 402 return; 403 } 404 // If the first subset succeeded, why would a later one fail? 405 // fall through to failure 406 default: 407 SkDebugf("[terminated] subset codec failed to decode (%d, %d, %d, %d) " 408 "with dimensions (%d x %d)\t error %d\n", 409 x, y, decodeInfo.width(), decodeInfo.height(), 410 W, H, result); 411 return; 412 } 413 // translate by the scaled height. 414 top += decodeInfo.height(); 415 } 416 // translate by the scaled width. 417 left += decodeInfo.width(); 418 } 419 SkDebugf("[terminated] Success!\n"); 420 break; 421 } 422 case 4: { //kAnimated_Mode 423 std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo(); 424 if (frameInfos.size() == 0) { 425 SkDebugf("[terminated] Not an animated image\n"); 426 break; 427 } 428 429 for (size_t i = 0; i < frameInfos.size(); i++) { 430 options.fFrameIndex = i; 431 auto result = codec->startIncrementalDecode(decodeInfo, bitmap.getPixels(), 432 bitmap.rowBytes(), &options); 433 if (SkCodec::kSuccess != result) { 434 SkDebugf("[terminated] failed to start incremental decode " 435 "in frame %d with error %d\n", i, result); 436 return; 437 } 438 439 result = codec->incrementalDecode(); 440 if (result == SkCodec::kIncompleteInput || result == SkCodec::kErrorInInput) { 441 SkDebugf("okay\n"); 442 // Frames beyond this one will not decode. 443 break; 444 } 445 if (result == SkCodec::kSuccess) { 446 SkDebugf("okay - decoded frame %d\n", i); 447 } else { 448 SkDebugf("[terminated] incremental decode failed with " 449 "error %d\n", result); 450 return; 451 } 452 } 453 SkDebugf("[terminated] Success!\n"); 454 break; 455 } 456 default: 457 SkDebugf("[terminated] Mode not implemented yet\n"); 458 } 459 460 dump_png(bitmap); 461 } 462 463 static void fuzz_skp(sk_sp<SkData> bytes) { 464 SkReadBuffer buf(bytes->data(), bytes->size()); 465 SkDebugf("Decoding\n"); 466 sk_sp<SkPicture> pic(SkPicture::MakeFromBuffer(buf)); 467 if (!pic) { 468 SkDebugf("[terminated] Couldn't decode as a picture.\n"); 469 return; 470 } 471 SkDebugf("Rendering\n"); 472 SkBitmap bitmap; 473 if (!FLAGS_dump.isEmpty()) { 474 SkIRect size = pic->cullRect().roundOut(); 475 bitmap.allocN32Pixels(size.width(), size.height()); 476 } 477 SkCanvas canvas(bitmap); 478 canvas.drawPicture(pic); 479 SkDebugf("[terminated] Success! Decoded and rendered an SkPicture!\n"); 480 dump_png(bitmap); 481 } 482 483 static void fuzz_skpipe(sk_sp<SkData> bytes) { 484 SkPipeDeserializer d; 485 SkDebugf("Decoding\n"); 486 sk_sp<SkPicture> pic(d.readPicture(bytes.get())); 487 if (!pic) { 488 SkDebugf("[terminated] Couldn't decode picture via SkPipe.\n"); 489 return; 490 } 491 SkDebugf("Rendering\n"); 492 SkBitmap bitmap; 493 SkCanvas canvas(bitmap); 494 canvas.drawPicture(pic); 495 SkDebugf("[terminated] Success! Decoded and rendered an SkPicture from SkPipe!\n"); 496 } 497 498 static void fuzz_icc(sk_sp<SkData> bytes) { 499 sk_sp<SkColorSpace> space(SkColorSpace::MakeICC(bytes->data(), bytes->size())); 500 if (!space) { 501 SkDebugf("[terminated] Couldn't decode ICC.\n"); 502 return; 503 } 504 SkDebugf("[terminated] Success! Decoded ICC.\n"); 505 } 506 507 static void fuzz_color_deserialize(sk_sp<SkData> bytes) { 508 sk_sp<SkColorSpace> space(SkColorSpace::Deserialize(bytes->data(), bytes->size())); 509 if (!space) { 510 SkDebugf("[terminated] Couldn't deserialize Colorspace.\n"); 511 return; 512 } 513 SkDebugf("[terminated] Success! deserialized Colorspace.\n"); 514 } 515 516 static void fuzz_path_deserialize(sk_sp<SkData> bytes) { 517 SkPath path; 518 SkReadBuffer buf(bytes->data(), bytes->size()); 519 buf.readPath(&path); 520 if (!buf.isValid()) { 521 SkDebugf("[terminated] Couldn't deserialize SkPath.\n"); 522 return; 523 } 524 525 auto s = SkSurface::MakeRasterN32Premul(1024, 1024); 526 s->getCanvas()->drawPath(path, SkPaint()); 527 SkDebugf("[terminated] Success! Initialized SkPath.\n"); 528 } 529 530 bool FuzzRegionDeserialize(sk_sp<SkData> bytes); 531 532 static void fuzz_region_deserialize(sk_sp<SkData> bytes) { 533 if (!FuzzRegionDeserialize(bytes)) { 534 SkDebugf("[terminated] Couldn't initialize SkRegion.\n"); 535 return; 536 } 537 SkDebugf("[terminated] Success! Initialized SkRegion.\n"); 538 } 539 540 static void fuzz_textblob_deserialize(sk_sp<SkData> bytes) { 541 SkReadBuffer buf(bytes->data(), bytes->size()); 542 auto tb = SkTextBlob::MakeFromBuffer(buf); 543 if (!buf.isValid()) { 544 SkDebugf("[terminated] Couldn't deserialize SkTextBlob.\n"); 545 return; 546 } 547 548 auto s = SkSurface::MakeRasterN32Premul(512, 512); 549 s->getCanvas()->drawTextBlob(tb, 200, 200, SkPaint()); 550 SkDebugf("[terminated] Success! Initialized SkTextBlob.\n"); 551 } 552 553 void FuzzRegionSetPath(Fuzz* fuzz); 554 555 static void fuzz_region_set_path(sk_sp<SkData> bytes) { 556 Fuzz fuzz(bytes); 557 FuzzRegionSetPath(&fuzz); 558 SkDebugf("[terminated] region_set_path didn't crash!\n"); 559 } 560 561 static void fuzz_filter_fuzz(sk_sp<SkData> bytes) { 562 const int BitmapSize = 24; 563 SkBitmap bitmap; 564 bitmap.allocN32Pixels(BitmapSize, BitmapSize); 565 SkCanvas canvas(bitmap); 566 canvas.clear(0x00000000); 567 568 auto flattenable = SkImageFilter::Deserialize(bytes->data(), bytes->size()); 569 570 // Adding some info, but the test passed if we got here without any trouble 571 if (flattenable != nullptr) { 572 SkDebugf("Valid stream detected.\n"); 573 // Let's see if using the filters can cause any trouble... 574 SkPaint paint; 575 paint.setImageFilter(flattenable); 576 canvas.save(); 577 canvas.clipRect(SkRect::MakeXYWH( 578 0, 0, SkIntToScalar(BitmapSize), SkIntToScalar(BitmapSize))); 579 580 // This call shouldn't crash or cause ASAN to flag any memory issues 581 // If nothing bad happens within this call, everything is fine 582 canvas.drawBitmap(bitmap, 0, 0, &paint); 583 584 SkDebugf("Filter DAG rendered successfully\n"); 585 canvas.restore(); 586 } else { 587 SkDebugf("Invalid stream detected.\n"); 588 } 589 590 SkDebugf("[terminated] Done\n"); 591 } 592 593 #if SK_SUPPORT_GPU 594 static void fuzz_sksl2glsl(sk_sp<SkData> bytes) { 595 SkSL::Compiler compiler; 596 SkSL::String output; 597 SkSL::Program::Settings settings; 598 sk_sp<GrShaderCaps> caps = SkSL::ShaderCapsFactory::Default(); 599 settings.fCaps = caps.get(); 600 std::unique_ptr<SkSL::Program> program = compiler.convertProgram(SkSL::Program::kFragment_Kind, 601 SkSL::String((const char*) bytes->data()), 602 settings); 603 if (!program || !compiler.toGLSL(*program, &output)) { 604 SkDebugf("[terminated] Couldn't compile input.\n"); 605 return; 606 } 607 SkDebugf("[terminated] Success! Compiled input.\n"); 608 } 609 #endif 610