1 | /* |
2 | * Copyright (C) 2008-2019 Apple Inc. All Rights Reserved. |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
17 | * |
18 | */ |
19 | |
20 | #pragma once |
21 | |
22 | #include "ButterflyInlines.h" |
23 | #include "GCDeferralContextInlines.h" |
24 | #include "JSArray.h" |
25 | #include "JSCInlines.h" |
26 | #include "JSGlobalObject.h" |
27 | #include "ObjectConstructor.h" |
28 | #include "RegExpInlines.h" |
29 | #include "RegExpObject.h" |
30 | |
31 | namespace JSC { |
32 | |
33 | static const PropertyOffset RegExpMatchesArrayIndexPropertyOffset = 100; |
34 | static const PropertyOffset RegExpMatchesArrayInputPropertyOffset = 101; |
35 | static const PropertyOffset RegExpMatchesArrayGroupsPropertyOffset = 102; |
36 | |
37 | ALWAYS_INLINE JSArray* tryCreateUninitializedRegExpMatchesArray(ObjectInitializationScope& scope, GCDeferralContext* deferralContext, Structure* structure, unsigned initialLength) |
38 | { |
39 | VM& vm = scope.vm(); |
40 | unsigned vectorLength = initialLength; |
41 | if (vectorLength > MAX_STORAGE_VECTOR_LENGTH) |
42 | return 0; |
43 | |
44 | const bool = true; |
45 | Butterfly* butterfly = Butterfly::tryCreateUninitialized(vm, nullptr, 0, structure->outOfLineCapacity(), hasIndexingHeader, vectorLength * sizeof(EncodedJSValue), deferralContext); |
46 | if (UNLIKELY(!butterfly)) |
47 | return nullptr; |
48 | |
49 | butterfly->setVectorLength(vectorLength); |
50 | butterfly->setPublicLength(initialLength); |
51 | |
52 | for (unsigned i = initialLength; i < vectorLength; ++i) |
53 | butterfly->contiguous().atUnsafe(i).clear(); |
54 | |
55 | JSArray* result = JSArray::createWithButterfly(vm, deferralContext, structure, butterfly); |
56 | |
57 | const bool createUninitialized = true; |
58 | scope.notifyAllocated(result, createUninitialized); |
59 | return result; |
60 | } |
61 | |
62 | ALWAYS_INLINE JSArray* createRegExpMatchesArray( |
63 | VM& vm, JSGlobalObject* globalObject, JSString* input, const String& inputValue, |
64 | RegExp* regExp, unsigned startOffset, MatchResult& result) |
65 | { |
66 | if (validateDFGDoesGC) |
67 | RELEASE_ASSERT(vm.heap.expectDoesGC()); |
68 | |
69 | Vector<int, 32> subpatternResults; |
70 | int position = regExp->matchInline(vm, inputValue, startOffset, subpatternResults); |
71 | if (position == -1) { |
72 | result = MatchResult::failed(); |
73 | return nullptr; |
74 | } |
75 | |
76 | result.start = position; |
77 | result.end = subpatternResults[1]; |
78 | |
79 | JSArray* array; |
80 | |
81 | // FIXME: This should handle array allocation errors gracefully. |
82 | // https://bugs.webkit.org/show_bug.cgi?id=155144 |
83 | |
84 | unsigned numSubpatterns = regExp->numSubpatterns(); |
85 | bool hasNamedCaptures = regExp->hasNamedCaptures(); |
86 | JSObject* groups = hasNamedCaptures ? constructEmptyObject(vm, globalObject->nullPrototypeObjectStructure()) : nullptr; |
87 | Structure* matchStructure = globalObject->regExpMatchesArrayStructure(); |
88 | |
89 | auto setProperties = [&] () { |
90 | array->putDirect(vm, RegExpMatchesArrayIndexPropertyOffset, jsNumber(result.start)); |
91 | array->putDirect(vm, RegExpMatchesArrayInputPropertyOffset, input); |
92 | array->putDirect(vm, RegExpMatchesArrayGroupsPropertyOffset, hasNamedCaptures ? groups : jsUndefined()); |
93 | |
94 | ASSERT(!array->butterfly()->indexingHeader()->preCapacity(matchStructure)); |
95 | auto capacity = matchStructure->outOfLineCapacity(); |
96 | auto size = matchStructure->outOfLineSize(); |
97 | gcSafeZeroMemory(static_cast<JSValue*>(array->butterfly()->base(0, capacity)), (capacity - size) * sizeof(JSValue)); |
98 | }; |
99 | |
100 | if (UNLIKELY(globalObject->isHavingABadTime())) { |
101 | GCDeferralContext deferralContext(vm.heap); |
102 | ObjectInitializationScope scope(vm); |
103 | array = JSArray::tryCreateUninitializedRestricted(scope, &deferralContext, matchStructure, numSubpatterns + 1); |
104 | |
105 | // FIXME: we should probably throw an out of memory error here, but |
106 | // when making this change we should check that all clients of this |
107 | // function will correctly handle an exception being thrown from here. |
108 | // https://bugs.webkit.org/show_bug.cgi?id=169786 |
109 | RELEASE_ASSERT(array); |
110 | |
111 | setProperties(); |
112 | |
113 | array->initializeIndexWithoutBarrier(scope, 0, jsSubstringOfResolved(vm, &deferralContext, input, result.start, result.end - result.start)); |
114 | |
115 | for (unsigned i = 1; i <= numSubpatterns; ++i) { |
116 | int start = subpatternResults[2 * i]; |
117 | JSValue value; |
118 | if (start >= 0) |
119 | value = jsSubstringOfResolved(vm, &deferralContext, input, start, subpatternResults[2 * i + 1] - start); |
120 | else |
121 | value = jsUndefined(); |
122 | array->initializeIndexWithoutBarrier(scope, i, value); |
123 | } |
124 | } else { |
125 | GCDeferralContext deferralContext(vm.heap); |
126 | ObjectInitializationScope scope(vm); |
127 | array = tryCreateUninitializedRegExpMatchesArray(scope, &deferralContext, matchStructure, numSubpatterns + 1); |
128 | |
129 | // FIXME: we should probably throw an out of memory error here, but |
130 | // when making this change we should check that all clients of this |
131 | // function will correctly handle an exception being thrown from here. |
132 | // https://bugs.webkit.org/show_bug.cgi?id=169786 |
133 | RELEASE_ASSERT(array); |
134 | |
135 | setProperties(); |
136 | |
137 | array->initializeIndexWithoutBarrier(scope, 0, jsSubstringOfResolved(vm, &deferralContext, input, result.start, result.end - result.start), ArrayWithContiguous); |
138 | |
139 | for (unsigned i = 1; i <= numSubpatterns; ++i) { |
140 | int start = subpatternResults[2 * i]; |
141 | JSValue value; |
142 | if (start >= 0) |
143 | value = jsSubstringOfResolved(vm, &deferralContext, input, start, subpatternResults[2 * i + 1] - start); |
144 | else |
145 | value = jsUndefined(); |
146 | array->initializeIndexWithoutBarrier(scope, i, value, ArrayWithContiguous); |
147 | } |
148 | } |
149 | |
150 | // Now the object is safe to scan by GC. |
151 | |
152 | // We initialize the groups object late as it could allocate, which with the current API could cause |
153 | // allocations. |
154 | if (hasNamedCaptures) { |
155 | for (unsigned i = 1; i <= numSubpatterns; ++i) { |
156 | String groupName = regExp->getCaptureGroupName(i); |
157 | if (!groupName.isEmpty()) |
158 | groups->putDirect(vm, Identifier::fromString(vm, groupName), array->getIndexQuickly(i)); |
159 | } |
160 | } |
161 | return array; |
162 | } |
163 | |
164 | inline JSArray* createRegExpMatchesArray(JSGlobalObject* globalObject, JSString* string, RegExp* regExp, unsigned startOffset) |
165 | { |
166 | VM& vm = getVM(globalObject); |
167 | auto scope = DECLARE_THROW_SCOPE(vm); |
168 | |
169 | MatchResult ignoredResult; |
170 | String input = string->value(globalObject); |
171 | RETURN_IF_EXCEPTION(scope, { }); |
172 | |
173 | RELEASE_AND_RETURN(scope, createRegExpMatchesArray(vm, globalObject, string, input, regExp, startOffset, ignoredResult)); |
174 | } |
175 | JSArray* createEmptyRegExpMatchesArray(JSGlobalObject*, JSString*, RegExp*); |
176 | Structure* createRegExpMatchesArrayStructure(VM&, JSGlobalObject*); |
177 | Structure* createRegExpMatchesArraySlowPutStructure(VM&, JSGlobalObject*); |
178 | |
179 | } // namespace JSC |
180 | |