2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-2013 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 +----------------------------------------------------------------------+
17 #include <boost/dynamic_bitset.hpp>
18 #include <boost/next_prior.hpp>
20 #include "folly/gen/Base.h"
22 #include "hphp/hhbbc/representation.h"
23 #include "hphp/hhbbc/unit-util.h"
24 #include "hphp/hhbbc/cfg.h"
26 namespace HPHP
{ namespace HHBBC
{ namespace php
{
30 const StaticString
s_Closure("Closure");
31 const StaticString
s_invoke("__invoke");
33 //////////////////////////////////////////////////////////////////////
35 bool checkBlock(const php::Block
& b
) {
36 assert(!b
.hhbcs
.empty());
38 // No instructions in the middle of a block should have taken edges,
39 // or be an unconditional Jmp.
40 for (auto it
= begin(b
.hhbcs
); it
!= end(b
.hhbcs
); ++it
) {
41 assert(it
->op
!= Op::Jmp
&& "unconditional Jmp mid-block");
42 if (boost::next(it
) == end(b
.hhbcs
)) break;
43 forEachTakenEdge(*it
, [&] (php::Block
& b
) {
44 assert(!"Instruction in middle of block had a jump target");
48 // The factored exit list contains unique elements.
49 std::set
<borrowed_ptr
<const php::Block
>> exitSet
;
50 std::copy(begin(b
.factoredExits
), end(b
.factoredExits
),
51 std::inserter(exitSet
, begin(exitSet
)));
52 assert(exitSet
.size() == b
.factoredExits
.size());
57 bool checkParams(const php::Func
& f
) {
58 assert(f
.params
.size() <= f
.locals
.size());
59 for (uint32_t i
= 0; i
< f
.locals
.size(); ++i
) {
60 assert(f
.locals
[i
]->id
== i
);
62 for (uint32_t i
= 0; i
< f
.iters
.size(); ++i
) {
63 assert(f
.iters
[i
]->id
== i
);
66 // dvInit pointers are consistent in the parameters vector and on
68 for (uint32_t i
= 0; i
< f
.params
.size(); ++i
) {
69 assert(f
.params
[i
].dvEntryPoint
== f
.dvEntries
[i
]);
75 // N is usually small ... ;)
76 template<class Container
>
77 bool has_edge_linear(const Container
& c
,
78 borrowed_ptr
<const php::Block
> target
) {
79 return std::find(begin(c
), end(c
), target
) != end(c
);
82 void checkExnTreeBasic(boost::dynamic_bitset
<>& seenIds
,
83 borrowed_ptr
<const ExnNode
> node
,
84 borrowed_ptr
<const ExnNode
> expectedParent
) {
85 // All exnNode ids must be unique.
86 if (seenIds
.size() < node
->id
+ 1) {
87 seenIds
.resize(node
->id
+ 1);
89 assert(!seenIds
[node
->id
]);
90 seenIds
[node
->id
] = true;
92 // Parent pointers should point to the node that has a given node as
94 assert(node
->parent
== expectedParent
);
96 for (auto& c
: node
->children
) {
97 checkExnTreeBasic(seenIds
, borrow(c
), node
);
101 void checkFaultEntryRec(boost::dynamic_bitset
<>& seenBlocks
,
102 const php::Block
& faultEntry
,
103 const php::ExnNode
& exnNode
) {
104 // Loops in fault funclets could cause us to revisit the same block,
105 // so we track the ones we've seen.
106 if (seenBlocks
.size() < faultEntry
.id
+ 1) {
107 seenBlocks
.resize(faultEntry
.id
+ 1);
109 if (seenBlocks
[faultEntry
.id
]) return;
110 seenBlocks
[faultEntry
.id
] = true;
113 * All funclets aren't in the main section.
115 assert(faultEntry
.section
!= php::Block::Section::Main
);
118 * The fault blocks should all have factored exits to parent
119 * catches/faults, if there are any.
121 * Note: for now this is an invariant, but if we start pruning
122 * factoredExits this might need to change.
124 for (auto parent
= exnNode
.parent
; parent
; parent
= parent
->parent
) {
127 [&] (const TryRegion
& tr
) {
128 for (DEBUG_ONLY
auto& c
: tr
.catches
) {
129 assert(has_edge_linear(faultEntry
.factoredExits
, c
.second
));
132 [&] (const FaultRegion
& fr
) {
133 assert(has_edge_linear(faultEntry
.factoredExits
, fr
.faultEntry
));
138 // Note: right now we're only asserting about normal successors, but
139 // there can be exception-only successors for catch blocks inside of
140 // fault funclets for finally handlers. (Just going un-asserted for
142 forEachNormalSuccessor(faultEntry
, [&] (const php::Block
& succ
) {
143 checkFaultEntryRec(seenBlocks
, succ
, exnNode
);
147 void checkExnTreeMore(borrowed_ptr
<const ExnNode
> node
) {
148 // Fault entries have a few things to assert.
151 [&] (const FaultRegion
& fr
) {
152 boost::dynamic_bitset
<> seenBlocks
;
153 checkFaultEntryRec(seenBlocks
, *fr
.faultEntry
, *node
);
155 [&] (const TryRegion
& tr
) {}
158 for (auto& c
: node
->children
) checkExnTreeMore(borrow(c
));
161 bool checkExnTree(const php::Func
& f
) {
162 boost::dynamic_bitset
<> seenIds
;
163 for (auto& n
: f
.exnNodes
) checkExnTreeBasic(seenIds
, borrow(n
), nullptr);
165 // ExnNode ids are contiguous.
166 for (size_t i
= 0; i
< seenIds
.size(); ++i
) assert(seenIds
[i
] == true);
168 // The following assertions come after the above, because if the
169 // tree is totally clobbered it's easy for the wrong ones to fire.
170 for (auto& n
: f
.exnNodes
) checkExnTreeMore(borrow(n
));
174 bool checkName(SString name
) {
175 return isNSNormalized(name
);
178 //////////////////////////////////////////////////////////////////////
182 bool check(const php::Func
& f
) {
183 assert(checkParams(f
));
184 assert(checkName(f
.name
));
185 for (DEBUG_ONLY
auto& block
: f
.blocks
) assert(checkBlock(*block
));
188 * Some of these relationships may change as async/await
189 * implementation progresses. Asserting them now so they are
190 * revisited here if they aren't true anymore.
192 if (f
.isClosureBody
) assert(!f
.top
);
193 if (f
.isAsync
) assert(f
.isGeneratorBody
||
194 f
.innerGeneratorFunc
);
195 if (f
.isPairGenerator
) assert(f
.isGeneratorBody
);
196 if (f
.isGeneratorBody
) assert(f
.outerGeneratorFunc
);
198 if (f
.isClosureBody
) {
201 f
.cls
->parentName
->isame(s_Closure
.get()));
204 if (f
.isGeneratorFromClosure
) {
205 assert(f
.isGeneratorBody
);
206 // Inner generator functions from closures don't live on any
208 assert(f
.cls
== nullptr);
211 assert(!(f
.outerGeneratorFunc
&& f
.innerGeneratorFunc
));
212 if (f
.outerGeneratorFunc
) {
213 assert(f
.outerGeneratorFunc
->innerGeneratorFunc
== &f
);
215 if (f
.innerGeneratorFunc
) {
216 assert(f
.innerGeneratorFunc
->outerGeneratorFunc
== &f
);
219 boost::dynamic_bitset
<> seenId(f
.nextBlockId
);
220 for (auto& block
: f
.blocks
) {
221 assert(checkBlock(*block
));
223 // All blocks have unique ids in a given function; not necessarily
225 assert(block
->id
< f
.nextBlockId
);
226 assert(!seenId
.test(block
->id
));
227 seenId
.set(block
->id
);
230 assert(checkExnTree(f
));
235 bool check(const php::Class
& c
) {
236 assert(checkName(c
.name
));
237 for (DEBUG_ONLY
auto& m
: c
.methods
) assert(check(*m
));
239 // Some invariants about Closure classes.
240 auto const isClo
= c
.parentName
&& c
.parentName
->isame(s_Closure
.get());
241 if (c
.closureContextCls
) {
242 assert(c
.closureContextCls
->unit
== c
.unit
);
246 assert(c
.methods
.size() == 1);
247 assert(c
.methods
[0]->name
->isame(s_invoke
.get()));
248 assert(c
.methods
[0]->isClosureBody
);
249 assert(c
.hoistability
== PreClass::ClosureHoistable
);
251 assert(!c
.closureContextCls
);
257 bool check(const php::Unit
& u
) {
258 assert(check(*u
.pseudomain
));
259 for (DEBUG_ONLY
auto& c
: u
.classes
) assert(check(*c
));
260 for (DEBUG_ONLY
auto& f
: u
.funcs
) assert(check(*f
));
264 bool check(const php::Program
& p
) {
265 for (DEBUG_ONLY
auto& u
: p
.units
) assert(check(*u
));
269 //////////////////////////////////////////////////////////////////////