Add sub-controls for Hack array compat runtime checks
[hiphop-php.git] / hphp / runtime / base / exceptions.cpp
blob4eb093b4e01218d7037e16430bc3edceccaa0e22
1 /*
2 +----------------------------------------------------------------------+
3 | HipHop for PHP |
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/base/exceptions.h"
18 #include "hphp/system/systemlib.h"
19 #include "hphp/runtime/base/backtrace.h"
20 #include "hphp/runtime/base/execution-context.h"
21 #include "hphp/runtime/base/object-data.h"
22 #include "hphp/runtime/base/type-variant.h"
23 #include "hphp/runtime/ext/std/ext_std_errorfunc.h"
24 #include "hphp/runtime/vm/vm-regs.h"
26 namespace HPHP {
28 //////////////////////////////////////////////////////////////////////
30 const StaticString s_file("file");
31 const StaticString s_line("line");
32 const StaticString s_trace("trace");
33 const StaticString s_traceOpts("traceOpts");
34 const Slot s_fileIdx{3};
35 const Slot s_lineIdx{4};
36 const Slot s_traceIdx{5};
37 const Slot s_traceOptsIdx{0};
39 __thread int tl_exit_code{0};
41 ExtendedException::ExtendedException() : Exception() {
42 computeBacktrace();
45 ExtendedException::ExtendedException(const std::string &msg) {
46 m_msg = msg;
47 computeBacktrace();
50 ExtendedException::ExtendedException(SkipFrame, const std::string &msg) {
51 m_msg = msg;
52 computeBacktrace(true);
55 ExtendedException::ExtendedException(const char *fmt, ...) {
56 va_list ap;
57 va_start(ap, fmt);
58 format(fmt, ap);
59 va_end(ap);
60 computeBacktrace();
63 ExtendedException::ExtendedException(const std::string& msg,
64 ArrayData* backTrace)
65 : m_btp(backTrace)
67 m_msg = msg;
71 * Normally we wouldn't need an explicit copy/move-constructors, or
72 * copy/move-assignment operators, but we have to make sure m_key isn't copied.
75 ExtendedException::ExtendedException(const ExtendedException& other)
76 : Exception(other),
77 m_btp(other.m_btp),
78 m_silent(other.m_silent)
81 ExtendedException::ExtendedException(ExtendedException&& other) noexcept
82 : Exception(std::move(other)),
83 m_btp(std::move(other.m_btp)),
84 m_silent(other.m_silent)
87 ExtendedException&
88 ExtendedException::operator=(const ExtendedException& other) {
89 Exception::operator=(other);
90 m_btp = other.m_btp;
91 m_silent = other.m_silent;
92 return *this;
95 ExtendedException&
96 ExtendedException::operator=(ExtendedException&& other) noexcept {
97 Exception::operator=(std::move(other));
98 m_btp = std::move(other.m_btp);
99 m_silent = other.m_silent;
100 return *this;
103 Array ExtendedException::getBacktrace() const {
104 return Array(m_btp.get());
107 std::pair<String, int> ExtendedException::getFileAndLine() const {
108 String file = empty_string();
109 int line = 0;
110 Array bt = getBacktrace();
111 if (!bt.empty()) {
112 Array top = tvCastToArrayLike(bt.rvalAt(0).tv());
113 if (top.exists(s_file)) file = tvCastToString(top.rvalAt(s_file).tv());
114 if (top.exists(s_line)) line = tvCastToInt64(top.rvalAt(s_line).tv());
116 return std::make_pair(file, line);
119 void ExtendedException::computeBacktrace(bool skipFrame /* = false */) {
120 Array bt = createBacktrace(BacktraceArgs()
121 .skipTop(skipFrame)
122 .withSelf()
123 .withMetadata());
124 m_btp = bt.get();
127 //////////////////////////////////////////////////////////////////////
129 FatalErrorException::FatalErrorException(int, const char *msg, ...) {
130 va_list ap;
131 va_start(ap, msg);
132 format(msg, ap);
133 va_end(ap);
134 computeBacktrace();
137 FatalErrorException::FatalErrorException(const std::string& msg,
138 const Array& backtrace,
139 bool isRecoverable /* = false */)
140 : ExtendedException(msg, backtrace.get()), m_recoverable(isRecoverable)
143 //////////////////////////////////////////////////////////////////////
145 void throw_null_pointer_exception() {
146 throw ExtendedException("A null object pointer was used.");
149 void throw_invalid_object_type(const char* clsName) {
150 throw ExtendedException("Unexpected object type %s.", clsName);
153 void throw_not_implemented(const char* feature) {
154 throw ExtendedException("%s is not implemented yet.", feature);
157 void throw_not_supported(const char* feature, const char* reason) {
158 throw ExtendedException("%s is not supported: %s", feature, reason);
161 ///////////////////////////////////////////////////////////////////////////////
163 [[noreturn]]
164 void raise_fatal_error(const char* msg,
165 const Array& bt /* = null_array */,
166 bool recoverable /* = false */,
167 bool silent /* = false */,
168 bool throws /* = true */) {
169 if (RuntimeOption::PHP7_EngineExceptions && throws) {
170 VMRegAnchor _;
171 SystemLib::throwErrorObject(Variant(msg));
173 auto ex = bt.isNull() && !recoverable
174 ? FatalErrorException(msg)
175 : FatalErrorException(msg, bt, recoverable);
176 ex.setSilent(silent);
177 throw ex;
180 ///////////////////////////////////////////////////////////////////////////////
182 namespace {
183 DEBUG_ONLY bool vmfp_is_builtin() {
184 VMRegAnchor _;
185 auto const fp = vmfp();
186 return fp && fp->func()->isBuiltin();
189 DEBUG_ONLY bool is_throwable(ObjectData* throwable) {
190 auto const erCls = SystemLib::s_ErrorClass;
191 auto const exCls = SystemLib::s_ExceptionClass;
192 return throwable->instanceof(erCls) || throwable->instanceof(exCls);
195 DEBUG_ONLY bool throwable_has_expected_props() {
196 auto const erCls = SystemLib::s_ErrorClass;
197 auto const exCls = SystemLib::s_ExceptionClass;
198 return
199 erCls->lookupDeclProp(s_file.get()) == s_fileIdx &&
200 exCls->lookupDeclProp(s_file.get()) == s_fileIdx &&
201 erCls->lookupDeclProp(s_line.get()) == s_lineIdx &&
202 exCls->lookupDeclProp(s_line.get()) == s_lineIdx &&
203 erCls->lookupDeclProp(s_trace.get()) == s_traceIdx &&
204 exCls->lookupDeclProp(s_trace.get()) == s_traceIdx;
207 int64_t exception_get_trace_options() {
208 auto const exCls = SystemLib::s_ExceptionClass;
209 assertx(exCls->lookupSProp(s_traceOpts.get()) == s_traceOptsIdx);
210 assertx(exCls->needInitialization());
212 exCls->initialize();
213 auto const traceOptsTV = exCls->getSPropData(s_traceOptsIdx);
214 return traceOptsTV->m_type == KindOfInt64
215 ? traceOptsTV->m_data.num : 0;
219 void throwable_init_file_and_line_from_builtin(ObjectData* throwable) {
220 assertx(vmfp_is_builtin());
221 assertx(is_throwable(throwable));
222 assertx(throwable_has_expected_props());
224 auto const trace_rval = throwable->propRvalAtOffset(s_traceIdx);
226 if (trace_rval.type() == KindOfResource) {
227 auto bt = dyn_cast<CompactTrace>(Resource(trace_rval.val().pres));
228 assertx(bt);
230 for (auto& f : bt->frames()) {
231 if (!f.func || f.func->isBuiltin()) continue;
233 auto const opAtPrevPc = f.func->unit()->getOp(f.prevPc);
234 Offset pcAdjust = 0;
235 if (opAtPrevPc == Op::PopR ||
236 opAtPrevPc == Op::UnboxR ||
237 opAtPrevPc == Op::UnboxRNop) {
238 pcAdjust = 1;
241 auto const ln = f.func->unit()->getLineNumber(f.prevPc - pcAdjust);
242 tvSetIgnoreRef(
243 make_tv<KindOfInt64>(ln),
244 throwable->propLvalAtOffset(s_lineIdx)
247 if (auto fn = f.func->originalFilename()) {
248 tvSetIgnoreRef(
249 make_tv<KindOfPersistentString>(fn),
250 throwable->propLvalAtOffset(s_fileIdx)
252 } else {
253 tvSetIgnoreRef(
254 make_tv<KindOfPersistentString>(f.func->unit()->filepath()),
255 throwable->propLvalAtOffset(s_fileIdx)
258 return;
260 return;
263 assertx(isArrayType(trace_rval.type()));
264 auto const trace = trace_rval.val().parr;
265 for (ArrayIter iter(trace); iter; ++iter) {
266 assertx(iter.second().asTypedValue()->m_type == KindOfArray);
267 auto const frame = iter.second().asTypedValue()->m_data.parr;
268 auto const file = frame->rval(s_file.get());
269 auto const line = frame->rval(s_line.get());
270 if (file || line) {
271 if (file) {
272 auto const tv = file.tv();
273 tvSetIgnoreRef(
274 tvAssertCell(tv),
275 throwable->propLvalAtOffset(s_fileIdx)
278 if (line) {
279 auto const tv = line.tv();
280 tvSetIgnoreRef(
281 tvAssertCell(tv),
282 throwable->propLvalAtOffset(s_lineIdx)
285 return;
290 void throwable_init(ObjectData* throwable) {
291 assertx(is_throwable(throwable));
292 assertx(throwable_has_expected_props());
294 auto const trace_lval = throwable->propLvalAtOffset(s_traceIdx);
295 auto opts = exception_get_trace_options();
296 auto const filterOpts = opts & ~k_DEBUG_BACKTRACE_IGNORE_ARGS;
297 if (
298 !RuntimeOption::EvalEnableCompactBacktrace || filterOpts ||
299 (RuntimeOption::EnableArgsInBacktraces &&
300 opts != k_DEBUG_BACKTRACE_IGNORE_ARGS)
302 auto trace = HHVM_FN(debug_backtrace)(opts);
303 cellMove(make_tv<KindOfArray>(trace.detach()), trace_lval);
304 } else {
305 cellMove(
306 make_tv<KindOfResource>(createCompactBacktrace().detach()->hdr()),
307 trace_lval
311 VMRegAnchor _;
312 auto const fp = vmfp();
313 if (UNLIKELY(!fp)) return;
314 if (UNLIKELY(fp->func()->isBuiltin())) {
315 throwable_init_file_and_line_from_builtin(throwable);
316 } else {
317 auto const unit = fp->func()->unit();
318 auto const file = const_cast<StringData*>(unit->filepath());
319 auto const line = unit->getLineNumber(unit->offsetOf(vmpc()));
320 tvSetIgnoreRef(
321 make_tv<KindOfString>(file),
322 throwable->propLvalAtOffset(s_fileIdx)
324 tvSetIgnoreRef(
325 make_tv<KindOfInt64>(line),
326 throwable->propLvalAtOffset(s_lineIdx)
331 ///////////////////////////////////////////////////////////////////////////////