From b73209d78f8753fd5573115c5b72c625bd392955 Mon Sep 17 00:00:00 2001 From: "yusukes@chromium.org" Date: Tue, 10 Aug 2010 01:36:03 +0000 Subject: [PATCH] Addressed Tavis's comments in http://codereview.chromium.org/3023041/. - Modified 4-bytes 16.16 Fixed-point number decoder in ReadNextNumberFromType2CharString(). - Fixed off-by-one error in ExecuteType2CharStringOperator(). - Fixed checks of 'div' and 'endchar' operators. They were not strict enough. - Wrote ~1600 lines of test to verify if the modification above is valid. - Moved Type2CharStringOperator enum definition from cff_type2_charstriung.cc tocff_type2_charstriung.h to write the unit tests. Review: http://codereview.chromium.org/3027049 BUG=51070 TEST=http://code.google.com/p/ots/wiki/HowToTestOts (Verified that the modified OTS rejects all malformed fonts, and does not reject all non-malformed fonts. I tested ~3400 TrueType and ~450 OpenType fonts.) TEST=ran cff_type2_charstring_test. --- src/cff_type2_charstring.cc | 281 +++-- src/cff_type2_charstring.h | 53 + test/SConstruct | 5 +- test/cff_type2_charstring_test.cc | 1588 +++++++++++++++++++++++++++++ 4 files changed, 1762 insertions(+), 165 deletions(-) create mode 100644 test/cff_type2_charstring_test.cc diff --git a/src/cff_type2_charstring.cc b/src/cff_type2_charstring.cc index 6a0d57fe..2137c62a 100644 --- a/src/cff_type2_charstring.cc +++ b/src/cff_type2_charstring.cc @@ -7,73 +7,27 @@ #include "cff_type2_charstring.h" +#include #include #include -#include #include #include #include namespace { -// The list of Operators. See Appendix. A in Adobe Technical Note #5177. -enum Type2CharStringOperator { - kHStem = 1, - kVStem = 3, - kVMoveTo = 4, - kRLineTo = 5, - kHLineTo = 6, - kVLineTo = 7, - kRRCurveTo = 8, - kCallSubr = 10, - kReturn = 11, - kEndChar = 14, - kHStemHm = 18, - kHintMask = 19, - kCntrMask = 20, - kRMoveTo = 21, - kHMoveTo = 22, - kVStemHm = 23, - kRCurveLine = 24, - kRLineCurve = 25, - kVVCurveTo = 26, - kHHCurveTo = 27, - kCallGSubr = 29, - kVHCurveTo = 30, - kHVCurveTo = 31, - kAnd = (12 << 8) + 3, - kOr = (12 << 8) + 4, - kNot = (12 << 8) + 5, - kAbs = (12 << 8) + 9, - kAdd = (12 << 8) + 10, - kSub = (12 << 8) + 11, - kDiv = (12 << 8) + 12, - kNeg = (12 << 8) + 14, - kEq = (12 << 8) + 15, - kDrop = (12 << 8) + 18, - kPut = (12 << 8) + 20, - kGet = (12 << 8) + 21, - kIfElse = (12 << 8) + 22, - kRandom = (12 << 8) + 23, - kMul = (12 << 8) + 24, - kSqrt = (12 << 8) + 26, - kDup = (12 << 8) + 27, - kExch = (12 << 8) + 28, - kIndex = (12 << 8) + 29, - kRoll = (12 << 8) + 30, - kHFlex = (12 << 8) + 34, - kFlex = (12 << 8) + 35, - kHFlex1 = (12 << 8) + 36, - kFlex1 = (12 << 8) + 37, -}; - // Type 2 Charstring Implementation Limits. See Appendix. B in Adobe Technical // Note #5177. +const int32_t kMaxSubrsCount = 65536; const size_t kMaxCharStringLength = 65535; const size_t kMaxArgumentStack = 48; const size_t kMaxNumberOfStemHints = 96; const size_t kMaxSubrNesting = 10; +// |dummy_result| should be a huge positive integer so callsubr and callgsubr +// will fail with the dummy value. +const int32_t dummy_result = INT_MAX; + bool ExecuteType2CharString(size_t call_depth, const ots::CFFIndex& global_subrs_index, const ots::CFFIndex& local_subrs_index, @@ -85,101 +39,101 @@ bool ExecuteType2CharString(size_t call_depth, size_t *in_out_num_stems); // Converts |op| to a string and returns it. -const char *Type2CharStringOperatorToString(Type2CharStringOperator op) { +const char *Type2CharStringOperatorToString(ots::Type2CharStringOperator op) { switch (op) { - case kHStem: + case ots::kHStem: return "HStem"; - case kVStem: + case ots::kVStem: return "VStem"; - case kVMoveTo: + case ots::kVMoveTo: return "VMoveTo"; - case kRLineTo: + case ots::kRLineTo: return "RLineTo"; - case kHLineTo: + case ots::kHLineTo: return "HLineTo"; - case kVLineTo: + case ots::kVLineTo: return "VLineTo"; - case kRRCurveTo: + case ots::kRRCurveTo: return "RRCurveTo"; - case kCallSubr: + case ots::kCallSubr: return "CallSubr"; - case kReturn: + case ots::kReturn: return "Return"; - case kEndChar: + case ots::kEndChar: return "EndChar"; - case kHStemHm: + case ots::kHStemHm: return "HStemHm"; - case kHintMask: + case ots::kHintMask: return "HintMask"; - case kCntrMask: + case ots::kCntrMask: return "CntrMask"; - case kRMoveTo: + case ots::kRMoveTo: return "RMoveTo"; - case kHMoveTo: + case ots::kHMoveTo: return "HMoveTo"; - case kVStemHm: + case ots::kVStemHm: return "VStemHm"; - case kRCurveLine: + case ots::kRCurveLine: return "RCurveLine"; - case kRLineCurve: + case ots::kRLineCurve: return "RLineCurve"; - case kVVCurveTo: + case ots::kVVCurveTo: return "VVCurveTo"; - case kHHCurveTo: + case ots::kHHCurveTo: return "HHCurveTo"; - case kCallGSubr: + case ots::kCallGSubr: return "CallGSubr"; - case kVHCurveTo: + case ots::kVHCurveTo: return "VHCurveTo"; - case kHVCurveTo: + case ots::kHVCurveTo: return "HVCurveTo"; - case kAnd: + case ots::kAnd: return "And"; - case kOr: + case ots::kOr: return "Or"; - case kNot: + case ots::kNot: return "Not"; - case kAbs: + case ots::kAbs: return "Abs"; - case kAdd: + case ots::kAdd: return "Add"; - case kSub: + case ots::kSub: return "Sub"; - case kDiv: + case ots::kDiv: return "Div"; - case kNeg: + case ots::kNeg: return "Neg"; - case kEq: + case ots::kEq: return "Eq"; - case kDrop: + case ots::kDrop: return "Drop"; - case kPut: + case ots::kPut: return "Put"; - case kGet: + case ots::kGet: return "Get"; - case kIfElse: + case ots::kIfElse: return "IfElse"; - case kRandom: + case ots::kRandom: return "Random"; - case kMul: + case ots::kMul: return "Mul"; - case kSqrt: + case ots::kSqrt: return "Sqrt"; - case kDup: + case ots::kDup: return "Dup"; - case kExch: + case ots::kExch: return "Exch"; - case kIndex: + case ots::kIndex: return "Index"; - case kRoll: + case ots::kRoll: return "Roll"; - case kHFlex: + case ots::kHFlex: return "HFlex"; - case kFlex: + case ots::kFlex: return "Flex"; - case kHFlex1: + case ots::kHFlex1: return "HFlex1"; - case kFlex1: + case ots::kFlex1: return "Flex1"; } @@ -246,16 +200,13 @@ bool ReadNextNumberFromType2CharString(ots::Buffer *char_string, *out_number = -((static_cast(v) - 251) * 256) - static_cast(w) - 108; } else if (v == 255) { - uint32_t result = 0; - for (size_t i = 0; i < 4; ++i) { - if (!char_string->ReadU8(&v)) { - return OTS_FAILURE(); - } - result = (result << 8) + v; - } - // TODO(yusukes): |result| should be treated as 16.16 fixed-point number + // TODO(yusukes): We should not skip the 4 bytes. Note that when v is 255, + // we should treat the following 4-bytes as a 16.16 fixed-point number // rather than 32bit signed int. - *out_number = static_cast(result); + if (!char_string->Skip(4)) { + return OTS_FAILURE(); + } + *out_number = dummy_result; } else { return OTS_FAILURE(); } @@ -277,16 +228,13 @@ bool ExecuteType2CharStringOperator(int32_t op, bool *out_found_endchar, bool *in_out_found_width, size_t *in_out_num_stems) { - // |dummy_result| should be a huge positive integer so callsubr and callgsubr - // will fail with the dummy value. - const int32_t dummy_result = std::numeric_limits::max(); const size_t stack_size = argument_stack->size(); switch (op) { - case kCallSubr: - case kCallGSubr: { + case ots::kCallSubr: + case ots::kCallGSubr: { const ots::CFFIndex& subrs_index = - (op == kCallSubr ? local_subrs_index : global_subrs_index); + (op == ots::kCallSubr ? local_subrs_index : global_subrs_index); if (stack_size < 1) { return OTS_FAILURE(); @@ -314,7 +262,10 @@ bool ExecuteType2CharStringOperator(int32_t op, if (subr_number < 0) { return OTS_FAILURE(); } - if (subrs_index.offsets.size() <= static_cast(subr_number)) { + if (subr_number >= kMaxSubrsCount) { + return OTS_FAILURE(); + } + if (subrs_index.offsets.size() <= static_cast(subr_number + 1)) { return OTS_FAILURE(); // The number is out-of-bounds. } @@ -342,18 +293,18 @@ bool ExecuteType2CharStringOperator(int32_t op, in_out_num_stems); } - case kReturn: + case ots::kReturn: return true; - case kEndChar: + case ots::kEndChar: *out_found_endchar = true; *in_out_found_width = true; // just in case. return true; - case kHStem: - case kVStem: - case kHStemHm: - case kVStemHm: { + case ots::kHStem: + case ots::kVStem: + case ots::kHStemHm: + case ots::kVStemHm: { bool successful = false; if (stack_size < 2) { return OTS_FAILURE(); @@ -375,7 +326,7 @@ bool ExecuteType2CharStringOperator(int32_t op, return successful ? true : OTS_FAILURE(); } - case kRMoveTo: { + case ots::kRMoveTo: { bool successful = false; if (stack_size == 2) { successful = true; @@ -388,8 +339,8 @@ bool ExecuteType2CharStringOperator(int32_t op, return successful ? true : OTS_FAILURE(); } - case kVMoveTo: - case kHMoveTo: { + case ots::kVMoveTo: + case ots::kHMoveTo: { bool successful = false; if (stack_size == 1) { successful = true; @@ -402,8 +353,8 @@ bool ExecuteType2CharStringOperator(int32_t op, return successful ? true : OTS_FAILURE(); } - case kHintMask: - case kCntrMask: { + case ots::kHintMask: + case ots::kCntrMask: { bool successful = false; if (stack_size == 0) { successful = true; @@ -437,7 +388,7 @@ bool ExecuteType2CharStringOperator(int32_t op, return true; } - case kRLineTo: + case ots::kRLineTo: if (!(*in_out_found_width)) { // The first stack-clearing operator should be one of hstem, hstemhm, // vstem, vstemhm, cntrmask, hintmask, hmoveto, vmoveto, rmoveto, or @@ -454,8 +405,8 @@ bool ExecuteType2CharStringOperator(int32_t op, argument_stack->pop(); return true; - case kHLineTo: - case kVLineTo: + case ots::kHLineTo: + case ots::kVLineTo: if (!(*in_out_found_width)) { return OTS_FAILURE(); } @@ -466,7 +417,7 @@ bool ExecuteType2CharStringOperator(int32_t op, argument_stack->pop(); return true; - case kRRCurveTo: + case ots::kRRCurveTo: if (!(*in_out_found_width)) { return OTS_FAILURE(); } @@ -480,7 +431,7 @@ bool ExecuteType2CharStringOperator(int32_t op, argument_stack->pop(); return true; - case kRCurveLine: + case ots::kRCurveLine: if (!(*in_out_found_width)) { return OTS_FAILURE(); } @@ -494,7 +445,7 @@ bool ExecuteType2CharStringOperator(int32_t op, argument_stack->pop(); return true; - case kRLineCurve: + case ots::kRLineCurve: if (!(*in_out_found_width)) { return OTS_FAILURE(); } @@ -508,7 +459,7 @@ bool ExecuteType2CharStringOperator(int32_t op, argument_stack->pop(); return true; - case kVVCurveTo: + case ots::kVVCurveTo: if (!(*in_out_found_width)) { return OTS_FAILURE(); } @@ -523,7 +474,7 @@ bool ExecuteType2CharStringOperator(int32_t op, argument_stack->pop(); return true; - case kHHCurveTo: { + case ots::kHHCurveTo: { bool successful = false; if (!(*in_out_found_width)) { return OTS_FAILURE(); @@ -543,8 +494,8 @@ bool ExecuteType2CharStringOperator(int32_t op, return successful ? true : OTS_FAILURE(); } - case kVHCurveTo: - case kHVCurveTo: { + case ots::kVHCurveTo: + case ots::kHVCurveTo: { bool successful = false; if (!(*in_out_found_width)) { return OTS_FAILURE(); @@ -573,11 +524,11 @@ bool ExecuteType2CharStringOperator(int32_t op, return successful ? true : OTS_FAILURE(); } - case kAnd: - case kOr: - case kEq: - case kAdd: - case kSub: + case ots::kAnd: + case ots::kOr: + case ots::kEq: + case ots::kAdd: + case ots::kSub: if (stack_size < 2) { return OTS_FAILURE(); } @@ -588,9 +539,9 @@ bool ExecuteType2CharStringOperator(int32_t op, // arithmetic and conditional operations. return true; - case kNot: - case kAbs: - case kNeg: + case ots::kNot: + case ots::kAbs: + case ots::kNeg: if (stack_size < 1) { return OTS_FAILURE(); } @@ -600,45 +551,46 @@ bool ExecuteType2CharStringOperator(int32_t op, // arithmetic and conditional operations. return true; - case kDiv: + case ots::kDiv: // TODO(yusukes): Should detect div-by-zero errors. - if (stack_size < 1) { + if (stack_size < 2) { return OTS_FAILURE(); } argument_stack->pop(); + argument_stack->pop(); argument_stack->push(dummy_result); // TODO(yusukes): Implement this. We should push a real value for all // arithmetic and conditional operations. return true; - case kDrop: + case ots::kDrop: if (stack_size < 1) { return OTS_FAILURE(); } argument_stack->pop(); return true; - case kPut: - case kGet: - case kIndex: + case ots::kPut: + case ots::kGet: + case ots::kIndex: // For now, just call OTS_FAILURE since there is no way to check whether the // index argument, |i|, is out-of-bounds or not. Fortunately, no OpenType // fonts I have (except malicious ones!) use the operators. // TODO(yusukes): Implement them in a secure way. return OTS_FAILURE(); - case kRoll: + case ots::kRoll: // Likewise, just call OTS_FAILURE for kRoll since there is no way to check // whether |N| is smaller than the current stack depth or not. // TODO(yusukes): Implement them in a secure way. return OTS_FAILURE(); - case kRandom: + case ots::kRandom: // For now, we don't handle the 'random' operator since the operator makes // it hard to analyze hinting code statically. return OTS_FAILURE(); - case kIfElse: + case ots::kIfElse: if (stack_size < 4) { return OTS_FAILURE(); } @@ -651,7 +603,7 @@ bool ExecuteType2CharStringOperator(int32_t op, // arithmetic and conditional operations. return true; - case kMul: + case ots::kMul: // TODO(yusukes): Should detect overflows. if (stack_size < 2) { return OTS_FAILURE(); @@ -663,7 +615,7 @@ bool ExecuteType2CharStringOperator(int32_t op, // arithmetic and conditional operations. return true; - case kSqrt: + case ots::kSqrt: // TODO(yusukes): Should check if the argument is negative. if (stack_size < 1) { return OTS_FAILURE(); @@ -674,7 +626,7 @@ bool ExecuteType2CharStringOperator(int32_t op, // arithmetic and conditional operations. return true; - case kDup: + case ots::kDup: if (stack_size < 1) { return OTS_FAILURE(); } @@ -688,7 +640,7 @@ bool ExecuteType2CharStringOperator(int32_t op, // arithmetic and conditional operations. return true; - case kExch: + case ots::kExch: if (stack_size < 2) { return OTS_FAILURE(); } @@ -700,7 +652,7 @@ bool ExecuteType2CharStringOperator(int32_t op, // arithmetic and conditional operations. return true; - case kHFlex: + case ots::kHFlex: if (!(*in_out_found_width)) { return OTS_FAILURE(); } @@ -711,7 +663,7 @@ bool ExecuteType2CharStringOperator(int32_t op, argument_stack->pop(); return true; - case kFlex: + case ots::kFlex: if (!(*in_out_found_width)) { return OTS_FAILURE(); } @@ -722,7 +674,7 @@ bool ExecuteType2CharStringOperator(int32_t op, argument_stack->pop(); return true; - case kHFlex1: + case ots::kHFlex1: if (!(*in_out_found_width)) { return OTS_FAILURE(); } @@ -733,7 +685,7 @@ bool ExecuteType2CharStringOperator(int32_t op, argument_stack->pop(); return true; - case kFlex1: + case ots::kFlex1: if (!(*in_out_found_width)) { return OTS_FAILURE(); } @@ -824,7 +776,7 @@ bool ExecuteType2CharString(size_t call_depth, if (*out_found_endchar) { return true; } - if (operator_or_operand == kReturn) { + if (operator_or_operand == ots::kReturn) { return true; } } @@ -927,6 +879,9 @@ bool ValidateType2CharStringIndex( &found_endchar, &found_width, &num_stems)) { return OTS_FAILURE(); } + if (!found_endchar) { + return OTS_FAILURE(); + } } return true; } diff --git a/src/cff_type2_charstring.h b/src/cff_type2_charstring.h index 4e6ff6a5..dbaea75b 100644 --- a/src/cff_type2_charstring.h +++ b/src/cff_type2_charstring.h @@ -42,6 +42,59 @@ bool ValidateType2CharStringIndex( const CFFIndex *local_subrs, Buffer *cff_table); +// The list of Operators. See Appendix. A in Adobe Technical Note #5177. +enum Type2CharStringOperator { + kHStem = 1, + kVStem = 3, + kVMoveTo = 4, + kRLineTo = 5, + kHLineTo = 6, + kVLineTo = 7, + kRRCurveTo = 8, + kCallSubr = 10, + kReturn = 11, + kEndChar = 14, + kHStemHm = 18, + kHintMask = 19, + kCntrMask = 20, + kRMoveTo = 21, + kHMoveTo = 22, + kVStemHm = 23, + kRCurveLine = 24, + kRLineCurve = 25, + kVVCurveTo = 26, + kHHCurveTo = 27, + kCallGSubr = 29, + kVHCurveTo = 30, + kHVCurveTo = 31, + kAnd = (12 << 8) + 3, + kOr = (12 << 8) + 4, + kNot = (12 << 8) + 5, + kAbs = (12 << 8) + 9, + kAdd = (12 << 8) + 10, + kSub = (12 << 8) + 11, + kDiv = (12 << 8) + 12, + kNeg = (12 << 8) + 14, + kEq = (12 << 8) + 15, + kDrop = (12 << 8) + 18, + kPut = (12 << 8) + 20, + kGet = (12 << 8) + 21, + kIfElse = (12 << 8) + 22, + kRandom = (12 << 8) + 23, + kMul = (12 << 8) + 24, + kSqrt = (12 << 8) + 26, + kDup = (12 << 8) + 27, + kExch = (12 << 8) + 28, + kIndex = (12 << 8) + 29, + kRoll = (12 << 8) + 30, + kHFlex = (12 << 8) + 34, + kFlex = (12 << 8) + 35, + kHFlex1 = (12 << 8) + 36, + kFlex1 = (12 << 8) + 37, + // Operators that are obsoleted or undocumented, such as 'blend', will be + // rejected. +}; + } // namespace ots #endif // OTS_CFF_TYPE2_CHARSTRING_H_ diff --git a/test/SConstruct b/test/SConstruct index eea29a11..c45f43fa 100644 --- a/test/SConstruct +++ b/test/SConstruct @@ -8,7 +8,7 @@ # Since the validator-checker tool might handle malicious font files, all hardening options for recent g++/ld are enabled just in case. # See http://wiki.debian.org/Hardening for details. -env = Environment(CCFLAGS = ['-O2', '-I../include', '-I/usr/include/freetype2', '-ggdb', '-Wall', '-W', '-Wno-unused-parameter', '-fno-strict-aliasing', '-fPIE', '-fstack-protector', '-D_FORTIFY_SOURCE=2', '-DOTS_DEBUG'], LINKFLAGS = ['-ggdb', '-Wl,-z,relro', '-Wl,-z,now', '-pie', '-lz']) +env = Environment(CCFLAGS = ['-O2', '-I../include', '-I../src', '-I/usr/include/freetype2', '-ggdb', '-Wall', '-W', '-Wno-unused-parameter', '-fno-strict-aliasing', '-fPIE', '-fstack-protector', '-D_FORTIFY_SOURCE=2', '-DOTS_DEBUG'], LINKFLAGS = ['-ggdb', '-Wl,-z,relro', '-Wl,-z,now', '-pie', '-lz']) # TODO(yusukes): better to use pkg-config freetype2 --cflags env.Library('../src/libots.a', @@ -35,7 +35,8 @@ env.Library('../src/libots.a', '../src/vdmx.cc', '../src/vorg.cc' ]) - + +env.Program('../test/cff_type2_charstring_test.cc', LIBS = ['ots', 'gtest_main'], LIBPATH = '../src') env.Program('../test/ot-sanitise.cc', LIBS = ['ots'], LIBPATH='../src') env.Program('../test/idempotent.cc', LIBS = ['ots', 'freetype', 'z', 'm'], LIBPATH='../src') env.Program('../test/perf.cc', LIBS = ['ots'], LIBPATH='../src') diff --git a/test/cff_type2_charstring_test.cc b/test/cff_type2_charstring_test.cc new file mode 100644 index 00000000..a2a76cfe --- /dev/null +++ b/test/cff_type2_charstring_test.cc @@ -0,0 +1,1588 @@ +// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cff_type2_charstring.h" + +#include + +#include +#include + +#include "cff.h" + +// Returns a biased number for callsubr and callgsubr operators. +#define GET_SUBR_NUMBER(n) ((n) - 107) +#define ARRAYSIZE(a) (sizeof(a) / sizeof(a[0])) + +namespace { + +// A constant which is used in AddSubr function below. +const int kOpPrefix = INT_MAX; + +// Encodes an operator |op| to 1 or more bytes and pushes them to |out_bytes|. +// Returns true if the conversion succeeds. +bool EncodeOperator(int op, std::vector *out_bytes) { + if (op < 0) { + return false; + } + if (op <= 11) { + out_bytes->push_back(op); + return true; + } + if (op == 12) { + return false; + } + if (op <= 27) { + out_bytes->push_back(op); + return true; + } + if (op == 28) { + return false; + } + if (op <= 31) { + out_bytes->push_back(op); + return true; + } + + const uint8_t upper = (op & 0xff00u) >> 8; + const uint8_t lower = op & 0xffu; + if (upper != 12) { + return false; + } + out_bytes->push_back(upper); + out_bytes->push_back(lower); + return true; +} + +// Encodes a number |num| to 1 or more bytes and pushes them to |out_bytes|. +// Returns true if the conversion succeeds. The function does not support 16.16 +// Fixed number. +bool EncodeNumber(int num, std::vector *out_bytes) { + if (num >= -107 && num <= 107) { + out_bytes->push_back(num + 139); + return true; + } + if (num >= 108 && num <= 1131) { + const uint8_t v = ((num - 108) / 256) + 247; + const uint8_t w = (num - 108) % 256; + out_bytes->push_back(v); + out_bytes->push_back(w); + return true; + } + if (num <= -108 && num >= -1131) { + const uint8_t v = (-(num + 108) / 256) + 251; + const uint8_t w = -(num + 108) % 256; + out_bytes->push_back(v); + out_bytes->push_back(w); + return true; + } + if (num <= -32768 && num >= -32767) { + const uint8_t v = (num % 0xff00u) >> 8; + const uint8_t w = num % 0xffu; + out_bytes->push_back(28); + out_bytes->push_back(v); + out_bytes->push_back(w); + return true; + } + return false; +} + +// Adds a subroutine |subr| to |out_buffer| and |out_subr|. The contents of the +// subroutine is copied to |out_buffer|, and then the position of the subroutine +// in |out_buffer| is written to |out_subr|. Returns true on success. +bool AddSubr(const int *subr, size_t subr_len, + std::vector* out_buffer, ots::CFFIndex *out_subr) { + size_t pre_offset = out_buffer->size(); + for (size_t i = 0; i < subr_len; ++i) { + if (subr[i] != kOpPrefix) { + if (!EncodeNumber(subr[i], out_buffer)) { + return false; + } + } else { + if (i + 1 == subr_len) { + return false; + } + ++i; + if (!EncodeOperator(subr[i], out_buffer)) { + return false; + } + } + } + + ++(out_subr->count); + out_subr->off_size = 1; + if (out_subr->offsets.empty()) { + out_subr->offsets.push_back(pre_offset); + } + out_subr->offsets.push_back(out_buffer->size()); + return true; +} + +// Validates |char_string| and returns true if it's valid. +bool Validate(const int *char_string, size_t char_string_len, + const int *global_subrs, size_t global_subrs_len, + const int *local_subrs, size_t local_subrs_len) { + std::vector buffer; + ots::CFFIndex char_strings_index; + ots::CFFIndex global_subrs_index; + ots::CFFIndex local_subrs_index; + + if (char_string) { + if (!AddSubr(char_string, char_string_len, + &buffer, &char_strings_index)) { + return false; + } + } + if (global_subrs) { + if (!AddSubr(global_subrs, global_subrs_len, + &buffer, &global_subrs_index)) { + return false; + } + } + if (local_subrs) { + if (!AddSubr(local_subrs, local_subrs_len, + &buffer, &local_subrs_index)) { + return false; + } + } + + const std::map fd_select; // empty + const std::vector local_subrs_per_font; // empty + ots::Buffer ots_buffer(&buffer[0], buffer.size()); + + return ots::ValidateType2CharStringIndex(char_strings_index, + global_subrs_index, + fd_select, + local_subrs_per_font, + &local_subrs_index, + &ots_buffer); +} + +// Validates |char_string| and returns true if it's valid. +bool ValidateCharStrings(const int *char_string, size_t char_string_len) { + return Validate(char_string, char_string_len, NULL, 0, NULL, 0); +} + +} // namespace + +TEST(ValidateTest, TestRMoveTo) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kRMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, // width + 1, 2, kOpPrefix, ots::kRMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kRMoveTo, + 1, 2, 3, kOpPrefix, ots::kRMoveTo, // invalid number of args + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHMoveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kHMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, // width + 1, kOpPrefix, ots::kHMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kHMoveTo, + 1, 2, kOpPrefix, ots::kHMoveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVMoveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, // width + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, kOpPrefix, ots::kVMoveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRLineTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, kOpPrefix, ots::kRLineTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, kOpPrefix, ots::kRLineTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, kOpPrefix, ots::kRLineTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kRLineTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHLineTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kHLineTo, + 1, 2, kOpPrefix, ots::kHLineTo, + 1, 2, 3, kOpPrefix, ots::kHLineTo, + 1, 2, 3, 4, kOpPrefix, ots::kHLineTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kHLineTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kHLineTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kHLineTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVLineTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kVLineTo, + 1, 2, kOpPrefix, ots::kVLineTo, + 1, 2, 3, kOpPrefix, ots::kVLineTo, + 1, 2, 3, 4, kOpPrefix, ots::kVLineTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kVLineTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kVLineTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVLineTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRRCurveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, kOpPrefix, ots::kRRCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, kOpPrefix, ots::kRRCurveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kRRCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, kOpPrefix, ots::kRRCurveTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHHCurveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, kOpPrefix, ots::kHHCurveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kHHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHHCurveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kHHCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, kOpPrefix, ots::kHHCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, kOpPrefix, ots::kHHCurveTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHVCurveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + // The first form. + 1, 2, 3, 4, kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + kOpPrefix, ots::kHVCurveTo, + // The second form. + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + kOpPrefix, ots::kHVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, kOpPrefix, ots::kHVCurveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kHVCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, kOpPrefix, ots::kHVCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kHVCurveTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRCurveLine) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRCurveLine, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + kOpPrefix, ots::kRCurveLine, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kRCurveLine, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + // can't be the first op. + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRCurveLine, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRLineCurve) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRLineCurve, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, kOpPrefix, ots::kRLineCurve, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kRLineCurve, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + // can't be the first op. + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kRLineCurve, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVHCurveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + // The first form. + 1, 2, 3, 4, kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + kOpPrefix, ots::kVHCurveTo, + // The second form. + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + kOpPrefix, ots::kVHCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, 24, 25, kOpPrefix, ots::kVHCurveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kVHCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, kOpPrefix, ots::kVHCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kVHCurveTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVVCurveTo) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, kOpPrefix, ots::kVVCurveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kVVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kVVCurveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kVVCurveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kVVCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, kOpPrefix, ots::kVVCurveTo, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kVVCurveTo, // can't be the first op. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestFlex) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kFlex, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kFlex, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, kOpPrefix, ots::kFlex, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, kOpPrefix, ots::kFlex, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHFlex) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kHFlex, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kHFlex, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, kOpPrefix, ots::kHFlex, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, kOpPrefix, ots::kHFlex, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHFlex1) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHFlex1, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kHFlex1, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, kOpPrefix, ots::kHFlex1, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, kOpPrefix, ots::kHFlex1, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestFlex1) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, kOpPrefix, ots::kFlex1, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kFlex1, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, kOpPrefix, ots::kFlex1, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, kOpPrefix, ots::kFlex1, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestEndChar) { + { + const int char_string[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string), + NULL, 0, + local_subrs, ARRAYSIZE(local_subrs))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } +} + +TEST(ValidateTest, TestHStem) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 0, // width + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 0, 1, 2, kOpPrefix, ots::kHStem, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kHStem, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVStem) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kVStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kVStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 0, // width + 1, 2, kOpPrefix, ots::kVStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 0, 1, 2, kOpPrefix, ots::kVStem, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kVStem, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHStemHm) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kHStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 0, // width + 1, 2, kOpPrefix, ots::kHStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 0, 1, 2, kOpPrefix, ots::kHStemHm, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kHStemHm, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestVStemHm) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kVStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kVStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 0, // width + 1, 2, kOpPrefix, ots::kVStemHm, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 0, 1, 2, kOpPrefix, ots::kVStemHm, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kVMoveTo, + 1, 2, 3, 4, 5, kOpPrefix, ots::kVStemHm, // invalid + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestHintMask) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kHintMask, 0x00, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + 3, 4, 5, 6, kOpPrefix, ots::kHintMask, 0x00, // vstem + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kHintMask, 0x00, // no stems to mask + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + 3, 4, 5, kOpPrefix, ots::kHintMask, 0x00, // invalid vstem + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestCntrMask) { + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kCntrMask, 0x00, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + 3, 4, 5, 6, kOpPrefix, ots::kCntrMask, 0x00, // vstem + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kCntrMask, 0x00, // no stems to mask + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, kOpPrefix, ots::kHStem, + 3, 4, 5, kOpPrefix, ots::kCntrMask, 0x00, // invalid vstem + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestAbs) { + { + const int char_string[] = { + -1, kOpPrefix, ots::kAbs, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kAbs, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestAdd) { + { + const int char_string[] = { + 0, 1, kOpPrefix, ots::kAdd, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kAdd, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestSub) { + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kSub, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kSub, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestDiv) { + // TODO(yusukes): Test div-by-zero. + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kDiv, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kDiv, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestNeg) { + { + const int char_string[] = { + -1, kOpPrefix, ots::kNeg, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kNeg, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRandom) { + { + const int char_string[] = { + kOpPrefix, ots::kRandom, // OTS rejects the operator. + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestMul) { + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kMul, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kMul, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestSqrt) { + // TODO(yusukes): Test negative numbers. + { + const int char_string[] = { + 4, kOpPrefix, ots::kSqrt, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kSqrt, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestDrop) { + { + const int char_string[] = { + 1, 1, kOpPrefix, ots::kAdd, + kOpPrefix, ots::kDrop, + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kDrop, // invalid + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestExch) { + { + const int char_string[] = { + 1, 1, kOpPrefix, ots::kAdd, + kOpPrefix, ots::kDup, + kOpPrefix, ots::kExch, + kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 1, kOpPrefix, ots::kAdd, + kOpPrefix, ots::kExch, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestIndex) { + { + const int char_string[] = { + 1, 2, 3, -1, kOpPrefix, ots::kIndex, // OTS rejects the operator. + kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestRoll) { + { + const int char_string[] = { + 1, 2, 2, 1, kOpPrefix, ots::kRoll, // OTS rejects the operator. + kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestDup) { + { + const int char_string[] = { + 1, 1, kOpPrefix, ots::kAdd, + kOpPrefix, ots::kDup, + kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kDup, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestPut) { + { + const int char_string[] = { + 1, 10, kOpPrefix, ots::kPut, // OTS rejects the operator. + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestGet) { + { + const int char_string[] = { + 1, 10, kOpPrefix, ots::kGet, // OTS rejects the operator. + 1, 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestAnd) { + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kAnd, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kAnd, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestOr) { + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kOr, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kOr, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestNot) { + { + const int char_string[] = { + 1, kOpPrefix, ots::kNot, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, ots::kNot, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestEq) { + { + const int char_string[] = { + 2, 1, kOpPrefix, ots::kEq, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, kOpPrefix, ots::kEq, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestIfElse) { + { + const int char_string[] = { + 1, 2, 3, 4, kOpPrefix, ots::kIfElse, + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, kOpPrefix, ots::kIfElse, // invalid + 2, kOpPrefix, ots::kHStem, + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestCallSubr) { + // Call valid subr. + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string), + NULL, 0, + local_subrs, ARRAYSIZE(local_subrs))); + } + // Call undefined subr. + { + const int char_string[] = { + GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + NULL, 0, + local_subrs, ARRAYSIZE(local_subrs))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + NULL, 0, + local_subrs, ARRAYSIZE(local_subrs))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestCallGSubr) { + // Call valid subr. + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } + // Call undefined subr. + { + const int char_string[] = { + GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(-1), kOpPrefix, ots::kCallGSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(1), kOpPrefix, ots::kCallGSubr, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestCallGSubrWithComputedValues) { + { + // OTS does not allow to call(g)subr with a subroutine number which is + // not a immediate value for safety. + const int char_string[] = { + 0, 0, kOpPrefix, ots::kAdd, + kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } +} + +TEST(ValidateTest, TestInfiniteLoop) { + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + NULL, 0, + local_subrs, ARRAYSIZE(local_subrs))); + } + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + const int global_subrs[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + NULL, 0)); + } + // mutual recursion which doesn't stop. + { + const int char_string[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + const int global_subrs[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallSubr, + }; + const int local_subrs[] = { + GET_SUBR_NUMBER(0), kOpPrefix, ots::kCallGSubr, + }; + EXPECT_FALSE(Validate(char_string, ARRAYSIZE(char_string), + global_subrs, ARRAYSIZE(global_subrs), + local_subrs, ARRAYSIZE(local_subrs))); + } +} + +TEST(ValidateTest, TestStackOverflow) { + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, + kOpPrefix, ots::kEndChar, + }; + EXPECT_TRUE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, + 1, 2, 3, 4, 5, 6, 7, 8, 9, // overflow + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestDeprecatedOperators) { + { + const int char_string[] = { + kOpPrefix, (12 << 8) + 0, // dotsection operator, which is not supported. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, 16, // 'blend'. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, (12 << 8) + 8, // 'store'. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + kOpPrefix, (12 << 8) + 13, // 'load'. + kOpPrefix, ots::kEndChar, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +} + +TEST(ValidateTest, TestUnterminatedCharString) { + // No endchar operator. + { + const int char_string[] = { + 123, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 123, 456, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } + { + const int char_string[] = { + 123, 456, kOpPrefix, ots::kReturn, + }; + EXPECT_FALSE(ValidateCharStrings(char_string, ARRAYSIZE(char_string))); + } +}