diff options
-rw-r--r-- | AUTHORS | 1 | ||||
-rw-r--r-- | ChangeLog | 4 | ||||
-rwxr-xr-x | emcc | 9 | ||||
-rwxr-xr-x | emscripten.py | 4 | ||||
-rw-r--r-- | src/library.js | 8 | ||||
-rw-r--r-- | src/parseTools.js | 34 | ||||
-rw-r--r-- | src/preamble.js | 33 | ||||
-rw-r--r-- | src/relooper/Relooper.cpp | 27 | ||||
-rw-r--r-- | src/relooper/Relooper.h | 4 | ||||
-rw-r--r-- | src/relooper/test.txt | 4 | ||||
-rw-r--r-- | src/runtime.js | 2 | ||||
-rw-r--r-- | src/shell.js | 12 | ||||
-rw-r--r-- | system/lib/libc/musl/src/regex/fnmatch.c | 299 | ||||
-rw-r--r-- | system/lib/libcextra.symbols | 1 | ||||
-rw-r--r-- | tests/core/fnmatch.c | 79 | ||||
-rw-r--r-- | tests/core/fnmatch.out | 23 | ||||
-rw-r--r-- | tests/core/test_alloca.in | 13 | ||||
-rw-r--r-- | tests/test_core.py | 9 | ||||
-rw-r--r-- | tests/test_other.py | 20 | ||||
-rw-r--r-- | tools/eliminator/asm-eliminator-test-output.js | 506 | ||||
-rw-r--r-- | tools/eliminator/asm-eliminator-test.js | 651 | ||||
-rw-r--r-- | tools/js-optimizer.js | 309 | ||||
-rw-r--r-- | tools/js_optimizer.py | 6 | ||||
-rw-r--r-- | tools/shared.py | 2 |
24 files changed, 1919 insertions, 141 deletions
@@ -118,3 +118,4 @@ a license to everyone to use it as detailed in LICENSE.) * Alexandre Perrot <alexandre.perrot@gmail.com> * Emerson José Silveira da Costa <emerson.costa@gmail.com> * Jari Vetoniemi <mailroxas@gmail.com> +* Sindre Sorhus <sindresorhus@gmail.com> @@ -12,12 +12,12 @@ Current trunk code - To see a list of commits in the active development branch 'incoming', which have not yet been packaged in a release, see https://github.com/kripken/emscripten/compare/1.8.2...incoming -v1.8.2: 1/4/2013 +v1.8.2: 1/4/2014 ------------------ - Fixed glGetFramebufferAttachmentParameteriv and an issue with glGetXXX when the returned value was null. - Full list of changes: https://github.com/kripken/emscripten/compare/1.8.1...1.8.2 -v1.8.1: 1/3/2013 +v1.8.1: 1/3/2014 ------------------ - Added support for WebGL hardware instancing extension. - Improved fastcomp native LLVM backend support. @@ -1196,7 +1196,6 @@ try: shared.Settings.ASM_JS = 1 assert shared.Settings.ALLOW_MEMORY_GROWTH == 0, 'memory growth not supported in fastcomp yet' assert shared.Settings.UNALIGNED_MEMORY == 0, 'forced unaligned memory not supported in fastcomp' - assert shared.Settings.SAFE_HEAP == 0, 'safe heap not supported in fastcomp yet' assert shared.Settings.CHECK_HEAP_ALIGN == 0, 'check heap align not supported in fastcomp yet' assert shared.Settings.SAFE_DYNCALLS == 0, 'safe dyncalls not supported in fastcomp' assert shared.Settings.RESERVED_FUNCTION_POINTERS == 0, 'reserved function pointers not supported in fastcomp' @@ -1230,6 +1229,9 @@ try: shared.Settings.CORRECT_OVERFLOWS = 1 assert not shared.Settings.PGO, 'cannot run PGO in ASM_JS mode' + if shared.Settings.SAFE_HEAP and not js_opts: + logging.warning('asm.js+SAFE_HEAP requires js opts to be run (-O1 or above by default)') + if shared.Settings.CORRECT_SIGNS >= 2 or shared.Settings.CORRECT_OVERFLOWS >= 2 or shared.Settings.CORRECT_ROUNDINGS >= 2: debug_level = 4 # must keep debug info to do line-by-line operations @@ -1582,6 +1584,7 @@ try: 'wctomb.c', ]], ['regex', [ + 'fnmatch.c', 'regcomp.c', 'regerror.c', 'regexec.c', @@ -1991,6 +1994,8 @@ try: if DEBUG: save_intermediate('closure') if js_opts: + if shared.Settings.ASM_JS and shared.Settings.SAFE_HEAP: js_optimizer_queue += ['safeHeap'] + if shared.Settings.OUTLINING_LIMIT > 0 and shared.Settings.ASM_JS: js_optimizer_queue += ['outline'] js_optimizer_extra_info['sizeToOutline'] = shared.Settings.OUTLINING_LIMIT @@ -1999,7 +2004,7 @@ try: js_optimizer_queue += ['registerize'] if opt_level > 0: - if debug_level < 2 and shared.Settings.ASM_JS: js_optimizer_queue = map(lambda p: p if p != 'registerize' else 'registerizeAndMinify', js_optimizer_queue) + if debug_level < 2 and shared.Settings.ASM_JS: js_optimizer_queue += ['minifyNames'] if debug_level == 0: js_optimizer_queue += ['minifyWhitespace'] if closure and shared.Settings.ASM_JS: diff --git a/emscripten.py b/emscripten.py index befad8d5..aeace63d 100755 --- a/emscripten.py +++ b/emscripten.py @@ -455,7 +455,7 @@ def emscript(infile, settings, outfile, libraries=[], compiler_engine=None, basic_funcs = ['abort', 'assert', 'asmPrintInt', 'asmPrintFloat'] + [m.replace('.', '_') for m in math_envs] if settings['RESERVED_FUNCTION_POINTERS'] > 0: basic_funcs.append('jsCall') - if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE', 'SAFE_HEAP_CLEAR'] + if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE'] if settings['CHECK_HEAP_ALIGN']: basic_funcs += ['CHECK_ALIGN_2', 'CHECK_ALIGN_4', 'CHECK_ALIGN_8'] if settings['ASSERTIONS']: basic_funcs += ['nullFunc'] @@ -956,7 +956,7 @@ def emscript_fast(infile, settings, outfile, libraries=[], compiler_engine=None, basic_funcs = ['abort', 'assert', 'asmPrintInt', 'asmPrintFloat'] + [m.replace('.', '_') for m in math_envs] if settings['RESERVED_FUNCTION_POINTERS'] > 0: basic_funcs.append('jsCall') - if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE', 'SAFE_HEAP_CLEAR'] + if settings['SAFE_HEAP']: basic_funcs += ['SAFE_HEAP_LOAD', 'SAFE_HEAP_STORE'] if settings['CHECK_HEAP_ALIGN']: basic_funcs += ['CHECK_ALIGN_2', 'CHECK_ALIGN_4', 'CHECK_ALIGN_8'] if settings['ASSERTIONS']: basic_funcs += ['nullFunc'] diff --git a/src/library.js b/src/library.js index fc120753..82aa5362 100644 --- a/src/library.js +++ b/src/library.js @@ -3810,6 +3810,7 @@ LibraryManager.library = { }, strnlen: function(ptr, num) { + num = num >>> 0; for (var i = 0; i < num; i++) { if ({{{ makeGetValue('ptr', 0, 'i8') }}} == 0) return i; ptr++; @@ -7323,6 +7324,12 @@ LibraryManager.library = { // netdb.h // ========================================================================== + __h_errno_state: 'allocate(1, "i32", ALLOC_STATIC)', + __h_errno_location__deps: ['__h_errno_state'], + __h_errno_location: function() { + return ___h_errno_state; + }, + // We can't actually resolve hostnames in the browser, so instead // we're generating fake IP addresses with lookup_name that we can // resolve later on with lookup_addr. @@ -7378,6 +7385,7 @@ LibraryManager.library = { gethostbyaddr: function (addr, addrlen, type) { if (type !== {{{ cDefine('AF_INET') }}}) { ___setErrNo(ERRNO_CODES.EAFNOSUPPORT); + // TODO: set h_errno return null; } addr = {{{ makeGetValue('addr', '0', 'i32') }}}; // addr is in_addr diff --git a/src/parseTools.js b/src/parseTools.js index be0cbcab..036ccfc1 100644 --- a/src/parseTools.js +++ b/src/parseTools.js @@ -1327,18 +1327,22 @@ function makeGetValue(ptr, pos, type, noNeedFirst, unsigned, ignore, align, noSa var printType = type; if (printType !== 'null' && printType[0] !== '#') printType = '"' + safeQuote(printType) + '"'; if (printType[0] === '#') printType = printType.substr(1); - return asmCoercion('SAFE_HEAP_LOAD(' + asmCoercion(offset, 'i32') + ', ' + (ASM_JS ? 0 : printType) + ', ' + (!!unsigned+0) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')', type); - } else { - var ret = makeGetSlabs(ptr, type, false, unsigned)[0] + '[' + getHeapOffset(offset, type, forceAsm) + ']'; - if (ASM_JS && (phase == 'funcs' || forceAsm)) { - ret = asmCoercion(ret, type); - } - if (ASM_HEAP_LOG) { - ret = makeInlineCalculation('(asmPrint' + (type in Runtime.FLOAT_TYPES ? 'Float' : 'Int') + '(' + (asmPrintCounter++) + ',' + asmCoercion('VALUE', type) + '), VALUE)', ret, - 'temp' + (type in Runtime.FLOAT_TYPES ? 'Double' : 'Int')); + if (ASM_JS) { + if (!ignore) return asmCoercion('SAFE_HEAP_LOAD(' + asmCoercion(offset, 'i32') + ', ' + Runtime.getNativeTypeSize(type) + ', ' + ((type in Runtime.FLOAT_TYPES)|0) + ')', type); + // else fall through + } else { + return asmCoercion('SAFE_HEAP_LOAD(' + offset + ', ' + (ASM_JS ? 0 : printType) + ', ' + (!!unsigned+0) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')', type); } - return ret; } + var ret = makeGetSlabs(ptr, type, false, unsigned)[0] + '[' + getHeapOffset(offset, type, forceAsm) + ']'; + if (ASM_JS && (phase == 'funcs' || forceAsm)) { + ret = asmCoercion(ret, type); + } + if (ASM_HEAP_LOG) { + ret = makeInlineCalculation('(asmPrint' + (type in Runtime.FLOAT_TYPES ? 'Float' : 'Int') + '(' + (asmPrintCounter++) + ',' + asmCoercion('VALUE', type) + '), VALUE)', ret, + 'temp' + (type in Runtime.FLOAT_TYPES ? 'Double' : 'Int')); + } + return ret; } function makeGetValueAsm(ptr, pos, type, unsigned) { @@ -1435,10 +1439,14 @@ function makeSetValue(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, var printType = type; if (printType !== 'null' && printType[0] !== '#') printType = '"' + safeQuote(printType) + '"'; if (printType[0] === '#') printType = printType.substr(1); - return 'SAFE_HEAP_STORE(' + asmCoercion(offset, 'i32') + ', ' + asmCoercion(value, type) + ', ' + (ASM_JS ? 0 : printType) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')'; - } else { - return makeGetSlabs(ptr, type, true).map(function(slab) { return slab + '[' + getHeapOffset(offset, type, forceAsm) + ']=' + value }).join(sep); + if (ASM_JS) { + if (!ignore) return asmCoercion('SAFE_HEAP_STORE(' + asmCoercion(offset, 'i32') + ', ' + asmCoercion(value, type) + ', ' + Runtime.getNativeTypeSize(type) + ', ' + ((type in Runtime.FLOAT_TYPES)|0) + ')', type); + // else fall through + } else { + return 'SAFE_HEAP_STORE(' + offset + ', ' + value + ', ' + (ASM_JS ? 0 : printType) + ', ' + ((!checkSafeHeap() || ignore)|0) + ')'; + } } + return makeGetSlabs(ptr, type, true).map(function(slab) { return slab + '[' + getHeapOffset(offset, type, forceAsm) + ']=' + value }).join(sep); } function makeSetValueAsm(ptr, pos, value, type, noNeedFirst, ignore, align, noSafe, sep, forcedAlign) { diff --git a/src/preamble.js b/src/preamble.js index ac6ee7b3..d70ef4b1 100644 --- a/src/preamble.js +++ b/src/preamble.js @@ -21,6 +21,7 @@ Module.print = Module.printErr = function(){}; #endif #if SAFE_HEAP +#if ASM_JS == 0 //======================================== // Debugging tools - Heap //======================================== @@ -166,6 +167,38 @@ function SAFE_HEAP_FILL_HISTORY(from, to, type) { } //========================================== +#else +// ASM_JS safe heap + +function getSafeHeapType(bytes, isFloat) { + switch (bytes) { + case 1: return 'i8'; + case 2: return 'i16'; + case 4: return isFloat ? 'float' : 'i32'; + case 8: return 'double'; + default: assert(0); + } +} + +function SAFE_HEAP_STORE(dest, value, bytes, isFloat) { +#if SAFE_HEAP_LOG + Module.print('SAFE_HEAP store: ' + [dest, value, bytes, isFloat]); +#endif + assert(dest > 0, 'segmentation fault'); + assert(dest % bytes === 0); + setValue(dest, value, getSafeHeapType(bytes, isFloat), 1); +} + +function SAFE_HEAP_LOAD(dest, bytes, isFloat) { +#if SAFE_HEAP_LOG + Module.print('SAFE_HEAP load: ' + [dest, bytes, isFloat]); +#endif + assert(dest > 0, 'segmentation fault'); + assert(dest % bytes === 0); + return getValue(dest, getSafeHeapType(bytes, isFloat), 1); +} + +#endif #endif #if CHECK_HEAP_ALIGN diff --git a/src/relooper/Relooper.cpp b/src/relooper/Relooper.cpp index d5772c62..204986da 100644 --- a/src/relooper/Relooper.cpp +++ b/src/relooper/Relooper.cpp @@ -322,12 +322,26 @@ void MultipleShape::RenderLoopPostfix() { void MultipleShape::Render(bool InLoop) { RenderLoopPrefix(); - bool First = true; + + // We know that blocks with the same Id were split from the same source, so their contents are identical and they are logically the same, so re-merge them here + typedef std::map<int, Shape*> IdShapeMap; + IdShapeMap IdMap; for (BlockShapeMap::iterator iter = InnerMap.begin(); iter != InnerMap.end(); iter++) { + int Id = iter->first->Id; + IdShapeMap::iterator Test = IdMap.find(Id); + if (Test != IdMap.end()) { + assert(Shape::IsSimple(iter->second) && Shape::IsSimple(Test->second)); // we can only merge simple blocks, something horrible has gone wrong if we see anything else + continue; + } + IdMap[iter->first->Id] = iter->second; + } + + bool First = true; + for (IdShapeMap::iterator iter = IdMap.begin(); iter != IdMap.end(); iter++) { if (AsmJS) { - PrintIndented("%sif ((label|0) == %d) {\n", First ? "" : "else ", iter->first->Id); + PrintIndented("%sif ((label|0) == %d) {\n", First ? "" : "else ", iter->first); } else { - PrintIndented("%sif (label == %d) {\n", First ? "" : "else ", iter->first->Id); + PrintIndented("%sif (label == %d) {\n", First ? "" : "else ", iter->first); } First = false; Indenter::Indent(); @@ -391,8 +405,8 @@ Relooper::~Relooper() { for (unsigned i = 0; i < Shapes.size(); i++) delete Shapes[i]; } -void Relooper::AddBlock(Block *New) { - New->Id = BlockIdCounter++; +void Relooper::AddBlock(Block *New, int Id) { + New->Id = Id == -1 ? BlockIdCounter++ : Id; Blocks.push_back(New); } @@ -446,8 +460,7 @@ void Relooper::Calculate(Block *Entry) { for (BlockSet::iterator iter = Original->BranchesIn.begin(); iter != Original->BranchesIn.end(); iter++) { Block *Prior = *iter; Block *Split = new Block(Original->Code, Original->BranchVar); - Parent->AddBlock(Split); - PrintDebug(" to %d\n", Split->Id); + Parent->AddBlock(Split, Original->Id); Split->BranchesIn.insert(Prior); Branch *Details = Prior->BranchesOut[Original]; Prior->BranchesOut[Split] = new Branch(Details->Condition, Details->Code); diff --git a/src/relooper/Relooper.h b/src/relooper/Relooper.h index 6b9394db..85adf359 100644 --- a/src/relooper/Relooper.h +++ b/src/relooper/Relooper.h @@ -57,7 +57,7 @@ struct Block { BlockBranchMap ProcessedBranchesOut; BlockSet ProcessedBranchesIn; Shape *Parent; // The shape we are directly inside - int Id; // A unique identifier, defined when added to relooper + int Id; // A unique identifier, defined when added to relooper. Note that this uniquely identifies a *logical* block - if we split it, the two instances have the same content *and* the same Id const char *Code; // The string representation of the code in this block. Owning pointer (we copy the input) const char *BranchVar; // If we have more than one branch out, the variable whose value determines where we go bool IsCheckedMultipleEntry; // If true, we are a multiple entry, so reaching us requires setting the label variable @@ -191,7 +191,7 @@ struct Relooper { Relooper(); ~Relooper(); - void AddBlock(Block *New); + void AddBlock(Block *New, int Id=-1); // Calculates the shapes void Calculate(Block *Entry); diff --git a/src/relooper/test.txt b/src/relooper/test.txt index cb02b867..82b02ad7 100644 --- a/src/relooper/test.txt +++ b/src/relooper/test.txt @@ -91,7 +91,7 @@ } default: { var $x_1 = $x_0; - label = 8; + label = 7; break L1; } } @@ -106,7 +106,7 @@ } } } - if (label == 8) { + if (label == 7) { // code 7 } // code 4 diff --git a/src/runtime.js b/src/runtime.js index cd3afb4b..1fc9e026 100644 --- a/src/runtime.js +++ b/src/runtime.js @@ -49,7 +49,7 @@ var RuntimeGenerator = { stackExit: function(initial, force) { if (initial === 0 && SKIP_STACK_IN_SMALL && !force) return ''; var ret = ''; - if (SAFE_HEAP) { + if (SAFE_HEAP && !ASM_JS) { ret += 'var i = sp; while ((i|0) < (STACKTOP|0)) { SAFE_HEAP_CLEAR(i|0); i = (i+1)|0 }'; } return ret += 'STACKTOP=sp'; diff --git a/src/shell.js b/src/shell.js index b41fbb51..84844c85 100644 --- a/src/shell.js +++ b/src/shell.js @@ -38,10 +38,10 @@ var ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIR if (ENVIRONMENT_IS_NODE) { // Expose functionality in the same simple way that the shells work // Note that we pollute the global namespace here, otherwise we break in node - Module['print'] = function print(x) { + if (!Module['print']) Module['print'] = function print(x) { process['stdout'].write(x + '\n'); }; - Module['printErr'] = function printErr(x) { + if (!Module['printErr']) Module['printErr'] = function printErr(x) { process['stderr'].write(x + '\n'); }; @@ -71,7 +71,7 @@ if (ENVIRONMENT_IS_NODE) { module['exports'] = Module; } else if (ENVIRONMENT_IS_SHELL) { - Module['print'] = print; + if (!Module['print']) Module['print'] = print; if (typeof printErr != 'undefined') Module['printErr'] = printErr; // not present in v8 or older sm if (typeof read != 'undefined') { @@ -107,16 +107,16 @@ else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) { } if (typeof console !== 'undefined') { - Module['print'] = function print(x) { + if (!Module['print']) Module['print'] = function print(x) { console.log(x); }; - Module['printErr'] = function printErr(x) { + if (!Module['printErr']) Module['printErr'] = function printErr(x) { console.log(x); }; } else { // Probably a worker, and without console.log. We can do very little here... var TRY_USE_DUMP = false; - Module['print'] = (TRY_USE_DUMP && (typeof(dump) !== "undefined") ? (function(x) { + if (!Module['print']) Module['print'] = (TRY_USE_DUMP && (typeof(dump) !== "undefined") ? (function(x) { dump(x); }) : (function(x) { // self.postMessage(x); // enable this if you want stdout to be sent as messages diff --git a/system/lib/libc/musl/src/regex/fnmatch.c b/system/lib/libc/musl/src/regex/fnmatch.c new file mode 100644 index 00000000..ffd3ea0d --- /dev/null +++ b/system/lib/libc/musl/src/regex/fnmatch.c @@ -0,0 +1,299 @@ +/* + * An implementation of what I call the "Sea of Stars" algorithm for + * POSIX fnmatch(). The basic idea is that we factor the pattern into + * a head component (which we match first and can reject without ever + * measuring the length of the string), an optional tail component + * (which only exists if the pattern contains at least one star), and + * an optional "sea of stars", a set of star-separated components + * between the head and tail. After the head and tail matches have + * been removed from the input string, the components in the "sea of + * stars" are matched sequentially by searching for their first + * occurrence past the end of the previous match. + * + * - Rich Felker, April 2012 + */ + +#include <string.h> +#include <fnmatch.h> +#include <stdlib.h> +#include <wchar.h> +#include <wctype.h> + +#define END -1 +#define UNMATCHABLE -2 +#define BRACKET -3 +#define QUESTION -4 +#define STAR -5 + +static int str_next(const char *str, size_t n, size_t *step) +{ + if (!n) { + *step = 0; + return 0; + } + if (str[0] >= 128U) { + wchar_t wc; + int k = mbtowc(&wc, str, n); + if (k<0) { + *step = 1; + return -1; + } + *step = k; + return wc; + } + *step = 1; + return str[0]; +} + +static int pat_next(const char *pat, size_t m, size_t *step, int flags) +{ + int esc = 0; + if (!m || !*pat) { + *step = 0; + return END; + } + *step = 1; + if (pat[0]=='\\' && !(flags & FNM_NOESCAPE)) { + *step = 2; + pat++; + esc = 1; + goto escaped; + } + if (pat[0]=='[') { + size_t k = 1; + if (k<m) if (pat[k] == '^' || pat[k] == '!') k++; + if (k<m) if (pat[k] == ']') k++; + for (; k<m && pat[k] && pat[k]!=']'; k++) { + if (k+1<m && pat[k+1] && pat[k]=='[' && (pat[k+1]==':' || pat[k+1]=='.' || pat[k+1]=='=')) { + int z = pat[k+1]; + k+=2; + if (k<m && pat[k]) k++; + while (k<m && pat[k] && (pat[k-1]!=z || pat[k]!=']')) k++; + if (k==m || !pat[k]) break; + } + } + if (k==m || !pat[k]) { + *step = 1; + return '['; + } + *step = k+1; + return BRACKET; + } + if (pat[0] == '*') + return STAR; + if (pat[0] == '?') + return QUESTION; +escaped: + if (pat[0] >= 128U) { + wchar_t wc; + int k = mbtowc(&wc, pat, m); + if (k<0) { + *step = 0; + return UNMATCHABLE; + } + *step = k + esc; + return wc; + } + return pat[0]; +} + +static int match_bracket(const char *p, int k) +{ + wchar_t wc; + int inv = 0; + p++; + if (*p=='^' || *p=='!') { + inv = 1; + p++; + } + if (*p==']') { + if (k==']') return !inv; + p++; + } else if (*p=='-') { + if (k=='-') return !inv; + p++; + } + wc = p[-1]; + for (; *p != ']'; p++) { + if (p[0]=='-' && p[1]!=']') { + wchar_t wc2; + int l = mbtowc(&wc2, p+1, 4); + if (l < 0) return 0; + if (wc<=wc2 && (unsigned)k-wc <= wc2-wc) return !inv; + p += l-1; + continue; + } + if (p[0]=='[' && (p[1]==':' || p[1]=='.' || p[1]=='=')) { + const char *p0 = p+2; + int z = p[1]; + p+=3; + while (p[-1]!=z || p[0]!=']') p++; + if (z == ':' && p-1-p0 < 16) { + char buf[16]; + memcpy(buf, p0, p-1-p0); + buf[p-1-p0] = 0; + if (iswctype(k, wctype(buf))) return !inv; + } + continue; + } + if (*p < 128U) { + wc = (unsigned char)*p; + } else { + int l = mbtowc(&wc, p, 4); + if (l < 0) return 0; + p += l-1; + } + if (wc==k) return !inv; + } + return inv; +} + +static int fnmatch_internal(const char *pat, size_t m, const char *str, size_t n, int flags) +{ + const char *p, *ptail, *endpat; + const char *s, *stail, *endstr; + size_t pinc, sinc, tailcnt=0; + int c, k; + + if (flags & FNM_PERIOD) { + if (*str == '.' && *pat != '.') + return FNM_NOMATCH; + } + for (;;) { + switch ((c = pat_next(pat, m, &pinc, flags))) { + case UNMATCHABLE: + return FNM_NOMATCH; + case STAR: + pat++; + m--; + break; + default: + k = str_next(str, n, &sinc); + if (k <= 0) + return (c==END) ? 0 : FNM_NOMATCH; + str += sinc; + n -= sinc; + if (c == BRACKET) { + if (!match_bracket(pat, k)) + return FNM_NOMATCH; + } else if (c != QUESTION && k != c) { + return FNM_NOMATCH; + } + pat+=pinc; + m-=pinc; + continue; + } + break; + } + + /* Compute real pat length if it was initially unknown/-1 */ + m = strnlen(pat, m); + endpat = pat + m; + + /* Find the last * in pat and count chars needed after it */ + for (p=ptail=pat; p<endpat; p+=pinc) { + switch (pat_next(p, endpat-p, &pinc, flags)) { + case UNMATCHABLE: + return FNM_NOMATCH; + case STAR: + tailcnt=0; + ptail = p+1; + break; + default: + tailcnt++; + break; + } + } + + /* Past this point we need not check for UNMATCHABLE in pat, + * because all of pat has already been parsed once. */ + + /* Compute real str length if it was initially unknown/-1 */ + n = strnlen(str, n); + endstr = str + n; + if (n < tailcnt) return FNM_NOMATCH; + + /* Find the final tailcnt chars of str, accounting for UTF-8. + * On illegal sequences we may get it wrong, but in that case + * we necessarily have a matching failure anyway. */ + for (s=endstr; s>str && tailcnt; tailcnt--) { + if (s[-1] < 128U) s--; + else while ((unsigned char)*--s-0x80U<0x40 && s>str); + } + if (tailcnt) return FNM_NOMATCH; + stail = s; + + /* Check that the pat and str tails match */ + p = ptail; + for (;;) { + c = pat_next(p, endpat-p, &pinc, flags); + p += pinc; + if ((k = str_next(s, endstr-s, &sinc)) <= 0) { + if (c != END) return FNM_NOMATCH; + break; + } + s += sinc; + if (c == BRACKET) { + if (!match_bracket(p-pinc, k)) + return FNM_NOMATCH; + } else if (c != QUESTION && k != c) { + return FNM_NOMATCH; + } + } + + /* We're all done with the tails now, so throw them out */ + endstr = stail; + endpat = ptail; + + /* Match pattern components until there are none left */ + while (pat<endpat) { + p = pat; + s = str; + for (;;) { + c = pat_next(p, endpat-p, &pinc, flags); + p += pinc; + /* Encountering * completes/commits a component */ + if (c == STAR) { + pat = p; + str = s; + break; + } + k = str_next(s, endstr-s, &sinc); + if (!k) + return FNM_NOMATCH; + if (c == BRACKET) { + if (!match_bracket(p-pinc, k)) + break; + } else if (c != QUESTION && k != c) { + break; + } + s += sinc; + } + if (c == STAR) continue; + /* If we failed, advance str, by 1 char if it's a valid + * char, or past all invalid bytes otherwise. */ + k = str_next(str, endstr-str, &sinc); + if (k > 0) str += sinc; + else for (str++; str_next(str, endstr-str, &sinc)<0; str++); + } + + return 0; +} + +int fnmatch(const char *pat, const char *str, int flags) +{ + const char *s, *p; + size_t inc; + int c; + if (flags & FNM_PATHNAME) for (;;) { + for (s=str; *s && *s!='/'; s++); + for (p=pat; (c=pat_next(p, -1, &inc, flags))!=END && c!='/'; p+=inc); + if (*s && *p!=*s) return FNM_NOMATCH; + if (fnmatch_internal(pat, p-pat, str, s-str, flags)) + return FNM_NOMATCH; + if (!*s && c==END) return 0; + str = s+1; + pat = p+1; + } + return fnmatch_internal(pat, -1, str, -1, flags); +} diff --git a/system/lib/libcextra.symbols b/system/lib/libcextra.symbols index 6f1039f1..54176b1d 100644 --- a/system/lib/libcextra.symbols +++ b/system/lib/libcextra.symbols @@ -187,3 +187,4 @@ T wmemmove T wmemset T wprintf + T fnmatch diff --git a/tests/core/fnmatch.c b/tests/core/fnmatch.c new file mode 100644 index 00000000..ebdb2009 --- /dev/null +++ b/tests/core/fnmatch.c @@ -0,0 +1,79 @@ +// Begin test_fnmatch.cpp +#include <fnmatch.h> +#include <iostream> +#include <vector> +#include <string> + +using namespace std; + +class TestCase { +public: + TestCase(const string& pattern, const string& testString, int flags, int expected) : + pattern(pattern), + testString(testString), + flags(flags), + expected(expected) + {} + string pattern; + string testString; + int flags; + int expected; +}; + +int main() +{ + vector<TestCase> testCases; + + testCases.push_back(TestCase("*","anything",0,0)); + testCases.push_back(TestCase("*.txt","readme.txt",0,0)); + testCases.push_back(TestCase("*.txt","readme.info",0,FNM_NOMATCH)); + testCases.push_back(TestCase("*.t?t","readme.txt",0,0)); + testCases.push_back(TestCase("*.t?t","readme.tot",0,0)); + testCases.push_back(TestCase("*.t?t","readme.txxt",0,FNM_NOMATCH)); + testCases.push_back(TestCase("[a-g]1","c1",0,0)); + testCases.push_back(TestCase("[a-g]1","i1",0,FNM_NOMATCH)); + testCases.push_back(TestCase("[!a-g]1","i1",0,0)); + testCases.push_back(TestCase("a\\*","anything",0,FNM_NOMATCH)); + testCases.push_back(TestCase("a\\*","a*",0,0)); + testCases.push_back(TestCase("a\\*","a*",FNM_NOESCAPE,FNM_NOMATCH)); + testCases.push_back(TestCase("a\\*","a\\*",FNM_NOESCAPE,0)); + testCases.push_back(TestCase("*readme","/etc/readme",0,0)); + testCases.push_back(TestCase("*readme","/etc/readme",FNM_PATHNAME,FNM_NOMATCH)); + testCases.push_back(TestCase("/*/readme","/etc/readme",FNM_PATHNAME,0)); + testCases.push_back(TestCase("*readme","/etc/.readme",0,0)); + testCases.push_back(TestCase("*readme",".readme",FNM_PERIOD,FNM_NOMATCH)); + testCases.push_back(TestCase("*.readme","/etc/.readme",FNM_PERIOD,0)); + testCases.push_back(TestCase("*.readme","/etc/.readme",FNM_PERIOD|FNM_PATHNAME,FNM_NOMATCH)); + testCases.push_back(TestCase("/*/.readme","/etc/.readme",FNM_PERIOD|FNM_PATHNAME,0)); + testCases.push_back(TestCase("ReAdME","readme",0,FNM_NOMATCH)); + + bool pass = true; + + for (vector<TestCase>::const_iterator it = testCases.begin(); it != testCases.end(); ++it) + { + int result = fnmatch(it->pattern.c_str(), it->testString.c_str(), it->flags); + if (result == it->expected) + cout << "Pass: "; + else + { + cout << "Fail: "; + pass = false; + } + + cout << "fnmatch(" << it->pattern << ", " << it->testString << ", " + << it->flags << ") returned " << result << ", expected " + << it->expected << endl; + } + + if (pass) + { + cout << "All tests passed." << endl; + return 0; + } + else + { + cout << "Some tests failed." << endl; + return 1; + } +} + diff --git a/tests/core/fnmatch.out b/tests/core/fnmatch.out new file mode 100644 index 00000000..303f7449 --- /dev/null +++ b/tests/core/fnmatch.out @@ -0,0 +1,23 @@ +Pass: fnmatch(*, anything, 0) returned 0, expected 0 +Pass: fnmatch(*.txt, readme.txt, 0) returned 0, expected 0 +Pass: fnmatch(*.txt, readme.info, 0) returned 1, expected 1 +Pass: fnmatch(*.t?t, readme.txt, 0) returned 0, expected 0 +Pass: fnmatch(*.t?t, readme.tot, 0) returned 0, expected 0 +Pass: fnmatch(*.t?t, readme.txxt, 0) returned 1, expected 1 +Pass: fnmatch([a-g]1, c1, 0) returned 0, expected 0 +Pass: fnmatch([a-g]1, i1, 0) returned 1, expected 1 +Pass: fnmatch([!a-g]1, i1, 0) returned 0, expected 0 +Pass: fnmatch(a\*, anything, 0) returned 1, expected 1 +Pass: fnmatch(a\*, a*, 0) returned 0, expected 0 |