js/src/vm/RegExpStatics.h

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/js/src/vm/RegExpStatics.h	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,542 @@
     1.4 +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
     1.5 + * vim: set ts=8 sts=4 et sw=4 tw=99:
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#ifndef vm_RegExpStatics_h
    1.11 +#define vm_RegExpStatics_h
    1.12 +
    1.13 +#include "gc/Marking.h"
    1.14 +#include "vm/MatchPairs.h"
    1.15 +#include "vm/RegExpObject.h"
    1.16 +#include "vm/Runtime.h"
    1.17 +
    1.18 +namespace js {
    1.19 +
    1.20 +class GlobalObject;
    1.21 +
    1.22 +class RegExpStatics
    1.23 +{
    1.24 +    /* The latest RegExp output, set after execution. */
    1.25 +    VectorMatchPairs        matches;
    1.26 +    HeapPtr<JSLinearString> matchesInput;
    1.27 +
    1.28 +    /*
    1.29 +     * The previous RegExp input, used to resolve lazy state.
    1.30 +     * A raw RegExpShared cannot be stored because it may be in
    1.31 +     * a different compartment via evalcx().
    1.32 +     */
    1.33 +    HeapPtr<JSAtom>         lazySource;
    1.34 +    RegExpFlag              lazyFlags;
    1.35 +    size_t                  lazyIndex;
    1.36 +
    1.37 +    /* The latest RegExp input, set before execution. */
    1.38 +    HeapPtr<JSString>       pendingInput;
    1.39 +    RegExpFlag              flags;
    1.40 +
    1.41 +    /*
    1.42 +     * If true, |matchesInput| and the |lazy*| fields may be used
    1.43 +     * to replay the last executed RegExp, and |matches| is invalid.
    1.44 +     */
    1.45 +    bool                    pendingLazyEvaluation;
    1.46 +
    1.47 +    /* Linkage for preserving RegExpStatics during nested RegExp execution. */
    1.48 +    RegExpStatics           *bufferLink;
    1.49 +    bool                    copied;
    1.50 +
    1.51 +  public:
    1.52 +    RegExpStatics() : bufferLink(nullptr), copied(false) { clear(); }
    1.53 +    static JSObject *create(JSContext *cx, GlobalObject *parent);
    1.54 +
    1.55 +  private:
    1.56 +    bool executeLazy(JSContext *cx);
    1.57 +
    1.58 +    inline void aboutToWrite();
    1.59 +    inline void copyTo(RegExpStatics &dst);
    1.60 +
    1.61 +    inline void restore();
    1.62 +    bool save(JSContext *cx, RegExpStatics *buffer) {
    1.63 +        JS_ASSERT(!buffer->copied && !buffer->bufferLink);
    1.64 +        buffer->bufferLink = bufferLink;
    1.65 +        bufferLink = buffer;
    1.66 +        if (!buffer->matches.allocOrExpandArray(matches.length())) {
    1.67 +            js_ReportOutOfMemory(cx);
    1.68 +            return false;
    1.69 +        }
    1.70 +        return true;
    1.71 +    }
    1.72 +
    1.73 +    inline void checkInvariants();
    1.74 +
    1.75 +    /*
    1.76 +     * Check whether the index at |checkValidIndex| is valid (>= 0).
    1.77 +     * If so, construct a string for it and place it in |*out|.
    1.78 +     * If not, place undefined in |*out|.
    1.79 +     */
    1.80 +    bool makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, MutableHandleValue out);
    1.81 +    bool createDependent(JSContext *cx, size_t start, size_t end, MutableHandleValue out);
    1.82 +
    1.83 +    void markFlagsSet(JSContext *cx);
    1.84 +
    1.85 +    struct InitBuffer {};
    1.86 +    explicit RegExpStatics(InitBuffer) : bufferLink(nullptr), copied(false) {}
    1.87 +
    1.88 +    friend class PreserveRegExpStatics;
    1.89 +    friend class AutoRegExpStaticsBuffer;
    1.90 +
    1.91 +  public:
    1.92 +    /* Mutators. */
    1.93 +    inline void updateLazily(JSContext *cx, JSLinearString *input,
    1.94 +                             RegExpShared *shared, size_t lastIndex);
    1.95 +    inline bool updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs);
    1.96 +
    1.97 +    void setMultiline(JSContext *cx, bool enabled) {
    1.98 +        aboutToWrite();
    1.99 +        if (enabled) {
   1.100 +            flags = RegExpFlag(flags | MultilineFlag);
   1.101 +            markFlagsSet(cx);
   1.102 +        } else {
   1.103 +            flags = RegExpFlag(flags & ~MultilineFlag);
   1.104 +        }
   1.105 +    }
   1.106 +
   1.107 +    inline void clear();
   1.108 +
   1.109 +    /* Corresponds to JSAPI functionality to set the pending RegExp input. */
   1.110 +    void reset(JSContext *cx, JSString *newInput, bool newMultiline) {
   1.111 +        aboutToWrite();
   1.112 +        clear();
   1.113 +        pendingInput = newInput;
   1.114 +        setMultiline(cx, newMultiline);
   1.115 +        checkInvariants();
   1.116 +    }
   1.117 +
   1.118 +    inline void setPendingInput(JSString *newInput);
   1.119 +
   1.120 +  public:
   1.121 +    /* Default match accessor. */
   1.122 +    const MatchPairs &getMatches() const {
   1.123 +        /* Safe: only used by String methods, which do not set lazy mode. */
   1.124 +        JS_ASSERT(!pendingLazyEvaluation);
   1.125 +        return matches;
   1.126 +    }
   1.127 +
   1.128 +    JSString *getPendingInput() const { return pendingInput; }
   1.129 +
   1.130 +    RegExpFlag getFlags() const { return flags; }
   1.131 +    bool multiline() const { return flags & MultilineFlag; }
   1.132 +
   1.133 +    /* Returns whether results for a non-empty match are present. */
   1.134 +    bool matched() const {
   1.135 +        /* Safe: only used by String methods, which do not set lazy mode. */
   1.136 +        JS_ASSERT(!pendingLazyEvaluation);
   1.137 +        JS_ASSERT(matches.pairCount() > 0);
   1.138 +        return matches[0].limit - matches[0].start > 0;
   1.139 +    }
   1.140 +
   1.141 +    void mark(JSTracer *trc) {
   1.142 +        /*
   1.143 +         * Changes to this function must also be reflected in
   1.144 +         * RegExpStatics::AutoRooter::trace().
   1.145 +         */
   1.146 +        if (matchesInput)
   1.147 +            MarkString(trc, &matchesInput, "res->matchesInput");
   1.148 +        if (lazySource)
   1.149 +            MarkString(trc, &lazySource, "res->lazySource");
   1.150 +        if (pendingInput)
   1.151 +            MarkString(trc, &pendingInput, "res->pendingInput");
   1.152 +    }
   1.153 +
   1.154 +    /* Value creators. */
   1.155 +
   1.156 +    bool createPendingInput(JSContext *cx, MutableHandleValue out);
   1.157 +    bool createLastMatch(JSContext *cx, MutableHandleValue out);
   1.158 +    bool createLastParen(JSContext *cx, MutableHandleValue out);
   1.159 +    bool createParen(JSContext *cx, size_t pairNum, MutableHandleValue out);
   1.160 +    bool createLeftContext(JSContext *cx, MutableHandleValue out);
   1.161 +    bool createRightContext(JSContext *cx, MutableHandleValue out);
   1.162 +
   1.163 +    /* Infallible substring creators. */
   1.164 +
   1.165 +    void getParen(size_t pairNum, JSSubString *out) const;
   1.166 +    void getLastMatch(JSSubString *out) const;
   1.167 +    void getLastParen(JSSubString *out) const;
   1.168 +    void getLeftContext(JSSubString *out) const;
   1.169 +    void getRightContext(JSSubString *out) const;
   1.170 +};
   1.171 +
   1.172 +class AutoRegExpStaticsBuffer : private JS::CustomAutoRooter
   1.173 +{
   1.174 +  public:
   1.175 +    explicit AutoRegExpStaticsBuffer(JSContext *cx
   1.176 +                                     MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
   1.177 +      : CustomAutoRooter(cx), statics(RegExpStatics::InitBuffer())
   1.178 +    {
   1.179 +        MOZ_GUARD_OBJECT_NOTIFIER_INIT;
   1.180 +    }
   1.181 +
   1.182 +    RegExpStatics& getStatics() { return statics; }
   1.183 +
   1.184 +  private:
   1.185 +    virtual void trace(JSTracer *trc) {
   1.186 +        if (statics.matchesInput) {
   1.187 +            MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics.matchesInput),
   1.188 +                                "AutoRegExpStaticsBuffer matchesInput");
   1.189 +        }
   1.190 +        if (statics.lazySource) {
   1.191 +            MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics.lazySource),
   1.192 +                                "AutoRegExpStaticsBuffer lazySource");
   1.193 +        }
   1.194 +        if (statics.pendingInput) {
   1.195 +            MarkStringRoot(trc, reinterpret_cast<JSString**>(&statics.pendingInput),
   1.196 +                                "AutoRegExpStaticsBuffer pendingInput");
   1.197 +        }
   1.198 +    }
   1.199 +
   1.200 +    RegExpStatics statics;
   1.201 +    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
   1.202 +};
   1.203 +
   1.204 +class PreserveRegExpStatics
   1.205 +{
   1.206 +    RegExpStatics * const original;
   1.207 +    AutoRegExpStaticsBuffer buffer;
   1.208 +
   1.209 +  public:
   1.210 +    explicit PreserveRegExpStatics(JSContext *cx, RegExpStatics *original)
   1.211 +     : original(original),
   1.212 +       buffer(cx)
   1.213 +    {}
   1.214 +
   1.215 +    bool init(JSContext *cx) {
   1.216 +        return original->save(cx, &buffer.getStatics());
   1.217 +    }
   1.218 +
   1.219 +    ~PreserveRegExpStatics() { original->restore(); }
   1.220 +};
   1.221 +
   1.222 +inline bool
   1.223 +RegExpStatics::createDependent(JSContext *cx, size_t start, size_t end, MutableHandleValue out)
   1.224 +{
   1.225 +    /* Private function: caller must perform lazy evaluation. */
   1.226 +    JS_ASSERT(!pendingLazyEvaluation);
   1.227 +
   1.228 +    JS_ASSERT(start <= end);
   1.229 +    JS_ASSERT(end <= matchesInput->length());
   1.230 +    JSString *str = js_NewDependentString(cx, matchesInput, start, end - start);
   1.231 +    if (!str)
   1.232 +        return false;
   1.233 +    out.setString(str);
   1.234 +    return true;
   1.235 +}
   1.236 +
   1.237 +inline bool
   1.238 +RegExpStatics::createPendingInput(JSContext *cx, MutableHandleValue out)
   1.239 +{
   1.240 +    /* Lazy evaluation need not be resolved to return the input. */
   1.241 +    out.setString(pendingInput ? pendingInput.get() : cx->runtime()->emptyString);
   1.242 +    return true;
   1.243 +}
   1.244 +
   1.245 +inline bool
   1.246 +RegExpStatics::makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum,
   1.247 +                         MutableHandleValue out)
   1.248 +{
   1.249 +    /* Private function: caller must perform lazy evaluation. */
   1.250 +    JS_ASSERT(!pendingLazyEvaluation);
   1.251 +
   1.252 +    bool checkWhich  = checkValidIndex % 2;
   1.253 +    size_t checkPair = checkValidIndex / 2;
   1.254 +
   1.255 +    if (matches.empty() || checkPair >= matches.pairCount() ||
   1.256 +        (checkWhich ? matches[checkPair].limit : matches[checkPair].start) < 0)
   1.257 +    {
   1.258 +        out.setString(cx->runtime()->emptyString);
   1.259 +        return true;
   1.260 +    }
   1.261 +    const MatchPair &pair = matches[pairNum];
   1.262 +    return createDependent(cx, pair.start, pair.limit, out);
   1.263 +}
   1.264 +
   1.265 +inline bool
   1.266 +RegExpStatics::createLastMatch(JSContext *cx, MutableHandleValue out)
   1.267 +{
   1.268 +    if (!executeLazy(cx))
   1.269 +        return false;
   1.270 +    return makeMatch(cx, 0, 0, out);
   1.271 +}
   1.272 +
   1.273 +inline bool
   1.274 +RegExpStatics::createLastParen(JSContext *cx, MutableHandleValue out)
   1.275 +{
   1.276 +    if (!executeLazy(cx))
   1.277 +        return false;
   1.278 +
   1.279 +    if (matches.empty() || matches.pairCount() == 1) {
   1.280 +        out.setString(cx->runtime()->emptyString);
   1.281 +        return true;
   1.282 +    }
   1.283 +    const MatchPair &pair = matches[matches.pairCount() - 1];
   1.284 +    if (pair.start == -1) {
   1.285 +        out.setString(cx->runtime()->emptyString);
   1.286 +        return true;
   1.287 +    }
   1.288 +    JS_ASSERT(pair.start >= 0 && pair.limit >= 0);
   1.289 +    JS_ASSERT(pair.limit >= pair.start);
   1.290 +    return createDependent(cx, pair.start, pair.limit, out);
   1.291 +}
   1.292 +
   1.293 +inline bool
   1.294 +RegExpStatics::createParen(JSContext *cx, size_t pairNum, MutableHandleValue out)
   1.295 +{
   1.296 +    JS_ASSERT(pairNum >= 1);
   1.297 +    if (!executeLazy(cx))
   1.298 +        return false;
   1.299 +
   1.300 +    if (matches.empty() || pairNum >= matches.pairCount()) {
   1.301 +        out.setString(cx->runtime()->emptyString);
   1.302 +        return true;
   1.303 +    }
   1.304 +    return makeMatch(cx, pairNum * 2, pairNum, out);
   1.305 +}
   1.306 +
   1.307 +inline bool
   1.308 +RegExpStatics::createLeftContext(JSContext *cx, MutableHandleValue out)
   1.309 +{
   1.310 +    if (!executeLazy(cx))
   1.311 +        return false;
   1.312 +
   1.313 +    if (matches.empty()) {
   1.314 +        out.setString(cx->runtime()->emptyString);
   1.315 +        return true;
   1.316 +    }
   1.317 +    if (matches[0].start < 0) {
   1.318 +        out.setUndefined();
   1.319 +        return true;
   1.320 +    }
   1.321 +    return createDependent(cx, 0, matches[0].start, out);
   1.322 +}
   1.323 +
   1.324 +inline bool
   1.325 +RegExpStatics::createRightContext(JSContext *cx, MutableHandleValue out)
   1.326 +{
   1.327 +    if (!executeLazy(cx))
   1.328 +        return false;
   1.329 +
   1.330 +    if (matches.empty()) {
   1.331 +        out.setString(cx->runtime()->emptyString);
   1.332 +        return true;
   1.333 +    }
   1.334 +    if (matches[0].limit < 0) {
   1.335 +        out.setUndefined();
   1.336 +        return true;
   1.337 +    }
   1.338 +    return createDependent(cx, matches[0].limit, matchesInput->length(), out);
   1.339 +}
   1.340 +
   1.341 +inline void
   1.342 +RegExpStatics::getParen(size_t pairNum, JSSubString *out) const
   1.343 +{
   1.344 +    JS_ASSERT(!pendingLazyEvaluation);
   1.345 +
   1.346 +    JS_ASSERT(pairNum >= 1 && pairNum < matches.pairCount());
   1.347 +    const MatchPair &pair = matches[pairNum];
   1.348 +    if (pair.isUndefined()) {
   1.349 +        *out = js_EmptySubString;
   1.350 +        return;
   1.351 +    }
   1.352 +    out->chars  = matchesInput->chars() + pair.start;
   1.353 +    out->length = pair.length();
   1.354 +}
   1.355 +
   1.356 +inline void
   1.357 +RegExpStatics::getLastMatch(JSSubString *out) const
   1.358 +{
   1.359 +    JS_ASSERT(!pendingLazyEvaluation);
   1.360 +
   1.361 +    if (matches.empty()) {
   1.362 +        *out = js_EmptySubString;
   1.363 +        return;
   1.364 +    }
   1.365 +    JS_ASSERT(matchesInput);
   1.366 +    out->chars = matchesInput->chars() + matches[0].start;
   1.367 +    JS_ASSERT(matches[0].limit >= matches[0].start);
   1.368 +    out->length = matches[0].length();
   1.369 +}
   1.370 +
   1.371 +inline void
   1.372 +RegExpStatics::getLastParen(JSSubString *out) const
   1.373 +{
   1.374 +    JS_ASSERT(!pendingLazyEvaluation);
   1.375 +
   1.376 +    /* Note: the first pair is the whole match. */
   1.377 +    if (matches.empty() || matches.pairCount() == 1) {
   1.378 +        *out = js_EmptySubString;
   1.379 +        return;
   1.380 +    }
   1.381 +    getParen(matches.parenCount(), out);
   1.382 +}
   1.383 +
   1.384 +inline void
   1.385 +RegExpStatics::getLeftContext(JSSubString *out) const
   1.386 +{
   1.387 +    JS_ASSERT(!pendingLazyEvaluation);
   1.388 +
   1.389 +    if (matches.empty()) {
   1.390 +        *out = js_EmptySubString;
   1.391 +        return;
   1.392 +    }
   1.393 +    out->chars = matchesInput->chars();
   1.394 +    out->length = matches[0].start;
   1.395 +}
   1.396 +
   1.397 +inline void
   1.398 +RegExpStatics::getRightContext(JSSubString *out) const
   1.399 +{
   1.400 +    JS_ASSERT(!pendingLazyEvaluation);
   1.401 +
   1.402 +    if (matches.empty()) {
   1.403 +        *out = js_EmptySubString;
   1.404 +        return;
   1.405 +    }
   1.406 +    out->chars = matchesInput->chars() + matches[0].limit;
   1.407 +    JS_ASSERT(matches[0].limit <= int(matchesInput->length()));
   1.408 +    out->length = matchesInput->length() - matches[0].limit;
   1.409 +}
   1.410 +
   1.411 +inline void
   1.412 +RegExpStatics::copyTo(RegExpStatics &dst)
   1.413 +{
   1.414 +    /* Destination buffer has already been reserved by save(). */
   1.415 +    if (!pendingLazyEvaluation)
   1.416 +        dst.matches.initArrayFrom(matches);
   1.417 +
   1.418 +    dst.matchesInput = matchesInput;
   1.419 +    dst.lazySource = lazySource;
   1.420 +    dst.lazyFlags = lazyFlags;
   1.421 +    dst.lazyIndex = lazyIndex;
   1.422 +    dst.pendingInput = pendingInput;
   1.423 +    dst.flags = flags;
   1.424 +    dst.pendingLazyEvaluation = pendingLazyEvaluation;
   1.425 +
   1.426 +    JS_ASSERT_IF(pendingLazyEvaluation, lazySource);
   1.427 +    JS_ASSERT_IF(pendingLazyEvaluation, matchesInput);
   1.428 +}
   1.429 +
   1.430 +inline void
   1.431 +RegExpStatics::aboutToWrite()
   1.432 +{
   1.433 +    if (bufferLink && !bufferLink->copied) {
   1.434 +        copyTo(*bufferLink);
   1.435 +        bufferLink->copied = true;
   1.436 +    }
   1.437 +}
   1.438 +
   1.439 +inline void
   1.440 +RegExpStatics::restore()
   1.441 +{
   1.442 +    if (bufferLink->copied)
   1.443 +        bufferLink->copyTo(*this);
   1.444 +    bufferLink = bufferLink->bufferLink;
   1.445 +}
   1.446 +
   1.447 +inline void
   1.448 +RegExpStatics::updateLazily(JSContext *cx, JSLinearString *input,
   1.449 +                            RegExpShared *shared, size_t lastIndex)
   1.450 +{
   1.451 +    JS_ASSERT(input && shared);
   1.452 +    aboutToWrite();
   1.453 +
   1.454 +    BarrieredSetPair<JSString, JSLinearString>(cx->zone(),
   1.455 +                                               pendingInput, input,
   1.456 +                                               matchesInput, input);
   1.457 +
   1.458 +    lazySource = shared->source;
   1.459 +    lazyFlags = shared->flags;
   1.460 +    lazyIndex = lastIndex;
   1.461 +    pendingLazyEvaluation = true;
   1.462 +}
   1.463 +
   1.464 +inline bool
   1.465 +RegExpStatics::updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs)
   1.466 +{
   1.467 +    JS_ASSERT(input);
   1.468 +    aboutToWrite();
   1.469 +
   1.470 +    /* Unset all lazy state. */
   1.471 +    pendingLazyEvaluation = false;
   1.472 +    this->lazySource = nullptr;
   1.473 +    this->lazyIndex = size_t(-1);
   1.474 +
   1.475 +    BarrieredSetPair<JSString, JSLinearString>(cx->zone(),
   1.476 +                                               pendingInput, input,
   1.477 +                                               matchesInput, input);
   1.478 +
   1.479 +    if (!matches.initArrayFrom(newPairs)) {
   1.480 +        js_ReportOutOfMemory(cx);
   1.481 +        return false;
   1.482 +    }
   1.483 +
   1.484 +    return true;
   1.485 +}
   1.486 +
   1.487 +inline void
   1.488 +RegExpStatics::clear()
   1.489 +{
   1.490 +    aboutToWrite();
   1.491 +
   1.492 +    matches.forgetArray();
   1.493 +    matchesInput = nullptr;
   1.494 +    lazySource = nullptr;
   1.495 +    lazyFlags = RegExpFlag(0);
   1.496 +    lazyIndex = size_t(-1);
   1.497 +    pendingInput = nullptr;
   1.498 +    flags = RegExpFlag(0);
   1.499 +    pendingLazyEvaluation = false;
   1.500 +}
   1.501 +
   1.502 +inline void
   1.503 +RegExpStatics::setPendingInput(JSString *newInput)
   1.504 +{
   1.505 +    aboutToWrite();
   1.506 +    pendingInput = newInput;
   1.507 +}
   1.508 +
   1.509 +inline void
   1.510 +RegExpStatics::checkInvariants()
   1.511 +{
   1.512 +#ifdef DEBUG
   1.513 +    if (pendingLazyEvaluation) {
   1.514 +        JS_ASSERT(lazySource);
   1.515 +        JS_ASSERT(matchesInput);
   1.516 +        JS_ASSERT(lazyIndex != size_t(-1));
   1.517 +        return;
   1.518 +    }
   1.519 +
   1.520 +    if (matches.empty()) {
   1.521 +        JS_ASSERT(!matchesInput);
   1.522 +        return;
   1.523 +    }
   1.524 +
   1.525 +    /* Pair count is non-zero, so there must be match pairs input. */
   1.526 +    JS_ASSERT(matchesInput);
   1.527 +    size_t mpiLen = matchesInput->length();
   1.528 +
   1.529 +    /* Both members of the first pair must be non-negative. */
   1.530 +    JS_ASSERT(!matches[0].isUndefined());
   1.531 +    JS_ASSERT(matches[0].limit >= 0);
   1.532 +
   1.533 +    /* Present pairs must be valid. */
   1.534 +    for (size_t i = 0; i < matches.pairCount(); i++) {
   1.535 +        if (matches[i].isUndefined())
   1.536 +            continue;
   1.537 +        const MatchPair &pair = matches[i];
   1.538 +        JS_ASSERT(mpiLen >= size_t(pair.limit) && pair.limit >= pair.start && pair.start >= 0);
   1.539 +    }
   1.540 +#endif /* DEBUG */
   1.541 +}
   1.542 +
   1.543 +} /* namespace js */
   1.544 +
   1.545 +#endif /* vm_RegExpStatics_h */

mercurial