gen_stub.php 116 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530
  1. #!/usr/bin/env php
  2. <?php declare(strict_types=1);
  3. use PhpParser\Comment\Doc as DocComment;
  4. use PhpParser\ConstExprEvaluator;
  5. use PhpParser\Node;
  6. use PhpParser\Node\Expr;
  7. use PhpParser\Node\Name;
  8. use PhpParser\Node\Stmt;
  9. use PhpParser\Node\Stmt\Class_;
  10. use PhpParser\Node\Stmt\Enum_;
  11. use PhpParser\Node\Stmt\Interface_;
  12. use PhpParser\Node\Stmt\Trait_;
  13. use PhpParser\PrettyPrinter\Standard;
  14. use PhpParser\PrettyPrinterAbstract;
  15. error_reporting(E_ALL);
  16. /**
  17. * @return FileInfo[]
  18. */
  19. function processDirectory(string $dir, Context $context): array {
  20. $pathNames = [];
  21. $it = new RecursiveIteratorIterator(
  22. new RecursiveDirectoryIterator($dir),
  23. RecursiveIteratorIterator::LEAVES_ONLY
  24. );
  25. foreach ($it as $file) {
  26. $pathName = $file->getPathName();
  27. if (preg_match('/\.stub\.php$/', $pathName)) {
  28. $pathNames[] = $pathName;
  29. }
  30. }
  31. // Make sure stub files are processed in a predictable, system-independent order.
  32. sort($pathNames);
  33. $fileInfos = [];
  34. foreach ($pathNames as $pathName) {
  35. $fileInfo = processStubFile($pathName, $context);
  36. if ($fileInfo) {
  37. $fileInfos[] = $fileInfo;
  38. }
  39. }
  40. return $fileInfos;
  41. }
  42. function processStubFile(string $stubFile, Context $context): ?FileInfo {
  43. try {
  44. if (!file_exists($stubFile)) {
  45. throw new Exception("File $stubFile does not exist");
  46. }
  47. $arginfoFile = str_replace('.stub.php', '_arginfo.h', $stubFile);
  48. $legacyFile = str_replace('.stub.php', '_legacy_arginfo.h', $stubFile);
  49. $stubCode = file_get_contents($stubFile);
  50. $stubHash = computeStubHash($stubCode);
  51. $oldStubHash = extractStubHash($arginfoFile);
  52. if ($stubHash === $oldStubHash && !$context->forceParse) {
  53. /* Stub file did not change, do not regenerate. */
  54. return null;
  55. }
  56. initPhpParser();
  57. $fileInfo = parseStubFile($stubCode);
  58. $arginfoCode = generateArgInfoCode($fileInfo, $stubHash);
  59. if (($context->forceRegeneration || $stubHash !== $oldStubHash) && file_put_contents($arginfoFile, $arginfoCode)) {
  60. echo "Saved $arginfoFile\n";
  61. }
  62. if ($fileInfo->generateLegacyArginfo) {
  63. $legacyFileInfo = clone $fileInfo;
  64. foreach ($legacyFileInfo->getAllFuncInfos() as $funcInfo) {
  65. $funcInfo->discardInfoForOldPhpVersions();
  66. }
  67. foreach ($legacyFileInfo->getAllPropertyInfos() as $propertyInfo) {
  68. $propertyInfo->discardInfoForOldPhpVersions();
  69. }
  70. $arginfoCode = generateArgInfoCode($legacyFileInfo, $stubHash);
  71. if (($context->forceRegeneration || $stubHash !== $oldStubHash) && file_put_contents($legacyFile, $arginfoCode)) {
  72. echo "Saved $legacyFile\n";
  73. }
  74. }
  75. return $fileInfo;
  76. } catch (Exception $e) {
  77. echo "In $stubFile:\n{$e->getMessage()}\n";
  78. exit(1);
  79. }
  80. }
  81. function computeStubHash(string $stubCode): string {
  82. return sha1(str_replace("\r\n", "\n", $stubCode));
  83. }
  84. function extractStubHash(string $arginfoFile): ?string {
  85. if (!file_exists($arginfoFile)) {
  86. return null;
  87. }
  88. $arginfoCode = file_get_contents($arginfoFile);
  89. if (!preg_match('/\* Stub hash: ([0-9a-f]+) \*/', $arginfoCode, $matches)) {
  90. return null;
  91. }
  92. return $matches[1];
  93. }
  94. class Context {
  95. /** @var bool */
  96. public $forceParse = false;
  97. /** @var bool */
  98. public $forceRegeneration = false;
  99. }
  100. class ArrayType extends SimpleType {
  101. /** @var Type */
  102. public $keyType;
  103. /** @var Type */
  104. public $valueType;
  105. public static function createGenericArray(): self
  106. {
  107. return new ArrayType(Type::fromString("int|string"), Type::fromString("mixed|ref"));
  108. }
  109. public function __construct(Type $keyType, Type $valueType)
  110. {
  111. parent::__construct("array", true);
  112. $this->keyType = $keyType;
  113. $this->valueType = $valueType;
  114. }
  115. public function toOptimizerTypeMask(): string {
  116. $typeMasks = [
  117. parent::toOptimizerTypeMask(),
  118. $this->keyType->toOptimizerTypeMaskForArrayKey(),
  119. $this->valueType->toOptimizerTypeMaskForArrayValue(),
  120. ];
  121. return implode("|", $typeMasks);
  122. }
  123. public function equals(SimpleType $other): bool {
  124. if (!parent::equals($other)) {
  125. return false;
  126. }
  127. assert(get_class($other) === self::class);
  128. return Type::equals($this->keyType, $other->keyType) &&
  129. Type::equals($this->valueType, $other->valueType);
  130. }
  131. }
  132. class SimpleType {
  133. /** @var string */
  134. public $name;
  135. /** @var bool */
  136. public $isBuiltin;
  137. public static function fromNode(Node $node): SimpleType {
  138. if ($node instanceof Node\Name) {
  139. if ($node->toLowerString() === 'static') {
  140. // PHP internally considers "static" a builtin type.
  141. return new SimpleType($node->toLowerString(), true);
  142. }
  143. if ($node->toLowerString() === 'self') {
  144. throw new Exception('The exact class name must be used instead of "self"');
  145. }
  146. assert($node->isFullyQualified());
  147. return new SimpleType($node->toString(), false);
  148. }
  149. if ($node instanceof Node\Identifier) {
  150. if ($node->toLowerString() === 'array') {
  151. return ArrayType::createGenericArray();
  152. }
  153. return new SimpleType($node->toLowerString(), true);
  154. }
  155. throw new Exception("Unexpected node type");
  156. }
  157. public static function fromString(string $typeString): SimpleType
  158. {
  159. switch (strtolower($typeString)) {
  160. case "void":
  161. case "null":
  162. case "false":
  163. case "true":
  164. case "bool":
  165. case "int":
  166. case "float":
  167. case "string":
  168. case "callable":
  169. case "iterable":
  170. case "object":
  171. case "resource":
  172. case "mixed":
  173. case "static":
  174. case "never":
  175. case "ref":
  176. return new SimpleType(strtolower($typeString), true);
  177. case "array":
  178. return ArrayType::createGenericArray();
  179. case "self":
  180. throw new Exception('The exact class name must be used instead of "self"');
  181. }
  182. $matches = [];
  183. $isArray = preg_match("/(.*)\s*\[\s*\]/", $typeString, $matches);
  184. if ($isArray) {
  185. return new ArrayType(Type::fromString("int"), Type::fromString($matches[1]));
  186. }
  187. $matches = [];
  188. $isArray = preg_match("/array\s*<\s*([A-Za-z0-9_-|]+)?(\s*,\s*)?([A-Za-z0-9_-|]+)?\s*>/i", $typeString, $matches);
  189. if ($isArray) {
  190. if (empty($matches[1]) || empty($matches[3])) {
  191. throw new Exception("array<> type hint must have both a key and a value");
  192. }
  193. return new ArrayType(Type::fromString($matches[1]), Type::fromString($matches[3]));
  194. }
  195. return new SimpleType($typeString, false);
  196. }
  197. public static function null(): SimpleType
  198. {
  199. return new SimpleType("null", true);
  200. }
  201. public static function void(): SimpleType
  202. {
  203. return new SimpleType("void", true);
  204. }
  205. protected function __construct(string $name, bool $isBuiltin) {
  206. $this->name = $name;
  207. $this->isBuiltin = $isBuiltin;
  208. }
  209. public function isScalar(): bool {
  210. return $this->isBuiltin && in_array($this->name, ["null", "false", "true", "bool", "int", "float"], true);
  211. }
  212. public function isNull(): bool {
  213. return $this->isBuiltin && $this->name === 'null';
  214. }
  215. public function toTypeCode(): string {
  216. assert($this->isBuiltin);
  217. switch ($this->name) {
  218. case "bool":
  219. return "_IS_BOOL";
  220. case "int":
  221. return "IS_LONG";
  222. case "float":
  223. return "IS_DOUBLE";
  224. case "string":
  225. return "IS_STRING";
  226. case "array":
  227. return "IS_ARRAY";
  228. case "object":
  229. return "IS_OBJECT";
  230. case "void":
  231. return "IS_VOID";
  232. case "callable":
  233. return "IS_CALLABLE";
  234. case "iterable":
  235. return "IS_ITERABLE";
  236. case "mixed":
  237. return "IS_MIXED";
  238. case "static":
  239. return "IS_STATIC";
  240. case "never":
  241. return "IS_NEVER";
  242. default:
  243. throw new Exception("Not implemented: $this->name");
  244. }
  245. }
  246. public function toTypeMask(): string {
  247. assert($this->isBuiltin);
  248. switch ($this->name) {
  249. case "null":
  250. return "MAY_BE_NULL";
  251. case "false":
  252. return "MAY_BE_FALSE";
  253. case "bool":
  254. return "MAY_BE_BOOL";
  255. case "int":
  256. return "MAY_BE_LONG";
  257. case "float":
  258. return "MAY_BE_DOUBLE";
  259. case "string":
  260. return "MAY_BE_STRING";
  261. case "array":
  262. return "MAY_BE_ARRAY";
  263. case "object":
  264. return "MAY_BE_OBJECT";
  265. case "callable":
  266. return "MAY_BE_CALLABLE";
  267. case "iterable":
  268. return "MAY_BE_ITERABLE";
  269. case "mixed":
  270. return "MAY_BE_ANY";
  271. case "void":
  272. return "MAY_BE_VOID";
  273. case "static":
  274. return "MAY_BE_STATIC";
  275. case "never":
  276. return "MAY_BE_NEVER";
  277. default:
  278. throw new Exception("Not implemented: $this->name");
  279. }
  280. }
  281. public function toOptimizerTypeMaskForArrayKey(): string {
  282. assert($this->isBuiltin);
  283. switch ($this->name) {
  284. case "int":
  285. return "MAY_BE_ARRAY_KEY_LONG";
  286. case "string":
  287. return "MAY_BE_ARRAY_KEY_STRING";
  288. default:
  289. throw new Exception("Type $this->name cannot be an array key");
  290. }
  291. }
  292. public function toOptimizerTypeMaskForArrayValue(): string {
  293. if (!$this->isBuiltin) {
  294. return "MAY_BE_ARRAY_OF_OBJECT";
  295. }
  296. switch ($this->name) {
  297. case "null":
  298. return "MAY_BE_ARRAY_OF_NULL";
  299. case "false":
  300. return "MAY_BE_ARRAY_OF_FALSE";
  301. case "bool":
  302. return "MAY_BE_ARRAY_OF_FALSE|MAY_BE_ARRAY_OF_TRUE";
  303. case "int":
  304. return "MAY_BE_ARRAY_OF_LONG";
  305. case "float":
  306. return "MAY_BE_ARRAY_OF_DOUBLE";
  307. case "string":
  308. return "MAY_BE_ARRAY_OF_STRING";
  309. case "array":
  310. return "MAY_BE_ARRAY_OF_ARRAY";
  311. case "object":
  312. return "MAY_BE_ARRAY_OF_OBJECT";
  313. case "resource":
  314. return "MAY_BE_ARRAY_OF_RESOURCE";
  315. case "mixed":
  316. return "MAY_BE_ARRAY_OF_ANY";
  317. case "ref":
  318. return "MAY_BE_ARRAY_OF_REF";
  319. default:
  320. throw new Exception("Type $this->name cannot be an array value");
  321. }
  322. }
  323. public function toOptimizerTypeMask(): string {
  324. if (!$this->isBuiltin) {
  325. return "MAY_BE_OBJECT";
  326. }
  327. switch ($this->name) {
  328. case "true":
  329. return "MAY_BE_TRUE";
  330. case "resource":
  331. return "MAY_BE_RESOURCE";
  332. case "callable":
  333. return "MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_LONG|MAY_BE_ARRAY_OF_STRING|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_OBJECT";
  334. case "iterable":
  335. return "MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_OBJECT";
  336. case "mixed":
  337. return "MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY";
  338. }
  339. return $this->toTypeMask();
  340. }
  341. public function toEscapedName(): string {
  342. return str_replace('\\', '\\\\', $this->name);
  343. }
  344. public function toVarEscapedName(): string {
  345. $name = str_replace('_', '__', $this->name);
  346. return str_replace('\\', '_', $this->name);
  347. }
  348. public function equals(SimpleType $other): bool {
  349. return $this->name === $other->name && $this->isBuiltin === $other->isBuiltin;
  350. }
  351. }
  352. class Type {
  353. /** @var SimpleType[] */
  354. public $types;
  355. public static function fromNode(Node $node): Type {
  356. if ($node instanceof Node\UnionType) {
  357. return new Type(array_map(['SimpleType', 'fromNode'], $node->types));
  358. }
  359. if ($node instanceof Node\NullableType) {
  360. return new Type(
  361. [
  362. SimpleType::fromNode($node->type),
  363. SimpleType::null(),
  364. ]
  365. );
  366. }
  367. return new Type([SimpleType::fromNode($node)]);
  368. }
  369. public static function fromString(string $typeString): self {
  370. $typeString .= "|";
  371. $simpleTypes = [];
  372. $simpleTypeOffset = 0;
  373. $inArray = false;
  374. $typeStringLength = strlen($typeString);
  375. for ($i = 0; $i < $typeStringLength; $i++) {
  376. $char = $typeString[$i];
  377. if ($char === "<") {
  378. $inArray = true;
  379. continue;
  380. }
  381. if ($char === ">") {
  382. $inArray = false;
  383. continue;
  384. }
  385. if ($inArray) {
  386. continue;
  387. }
  388. if ($char === "|") {
  389. $simpleTypeName = trim(substr($typeString, $simpleTypeOffset, $i - $simpleTypeOffset));
  390. $simpleTypes[] = SimpleType::fromString($simpleTypeName);
  391. $simpleTypeOffset = $i + 1;
  392. }
  393. }
  394. return new Type($simpleTypes);
  395. }
  396. /**
  397. * @param SimpleType[] $types
  398. */
  399. private function __construct(array $types) {
  400. $this->types = $types;
  401. }
  402. public function isScalar(): bool {
  403. foreach ($this->types as $type) {
  404. if (!$type->isScalar()) {
  405. return false;
  406. }
  407. }
  408. return true;
  409. }
  410. public function isNullable(): bool {
  411. foreach ($this->types as $type) {
  412. if ($type->isNull()) {
  413. return true;
  414. }
  415. }
  416. return false;
  417. }
  418. public function getWithoutNull(): Type {
  419. return new Type(
  420. array_filter(
  421. $this->types,
  422. function(SimpleType $type) {
  423. return !$type->isNull();
  424. }
  425. )
  426. );
  427. }
  428. public function tryToSimpleType(): ?SimpleType {
  429. $withoutNull = $this->getWithoutNull();
  430. if (count($withoutNull->types) === 1) {
  431. return $withoutNull->types[0];
  432. }
  433. return null;
  434. }
  435. public function toArginfoType(): ArginfoType {
  436. $classTypes = [];
  437. $builtinTypes = [];
  438. foreach ($this->types as $type) {
  439. if ($type->isBuiltin) {
  440. $builtinTypes[] = $type;
  441. } else {
  442. $classTypes[] = $type;
  443. }
  444. }
  445. return new ArginfoType($classTypes, $builtinTypes);
  446. }
  447. public function toOptimizerTypeMask(): string {
  448. $optimizerTypes = [];
  449. foreach ($this->types as $type) {
  450. $optimizerTypes[] = $type->toOptimizerTypeMask();
  451. }
  452. return implode("|", $optimizerTypes);
  453. }
  454. public function toOptimizerTypeMaskForArrayKey(): string {
  455. $typeMasks = [];
  456. foreach ($this->types as $type) {
  457. $typeMasks[] = $type->toOptimizerTypeMaskForArrayKey();
  458. }
  459. return implode("|", $typeMasks);
  460. }
  461. public function toOptimizerTypeMaskForArrayValue(): string {
  462. $typeMasks = [];
  463. foreach ($this->types as $type) {
  464. $typeMasks[] = $type->toOptimizerTypeMaskForArrayValue();
  465. }
  466. return implode("|", $typeMasks);
  467. }
  468. public function getTypeForDoc(DOMDocument $doc): DOMElement {
  469. if (count($this->types) > 1) {
  470. $typeElement = $doc->createElement('type');
  471. $typeElement->setAttribute("class", "union");
  472. foreach ($this->types as $type) {
  473. $unionTypeElement = $doc->createElement('type', $type->name);
  474. $typeElement->appendChild($unionTypeElement);
  475. }
  476. } else {
  477. $type = $this->types[0];
  478. if ($type->isBuiltin && strtolower($type->name) === "true") {
  479. $name = "bool";
  480. } else {
  481. $name = $type->name;
  482. }
  483. $typeElement = $doc->createElement('type', $name);
  484. }
  485. return $typeElement;
  486. }
  487. public static function equals(?Type $a, ?Type $b): bool {
  488. if ($a === null || $b === null) {
  489. return $a === $b;
  490. }
  491. if (count($a->types) !== count($b->types)) {
  492. return false;
  493. }
  494. for ($i = 0; $i < count($a->types); $i++) {
  495. if (!$a->types[$i]->equals($b->types[$i])) {
  496. return false;
  497. }
  498. }
  499. return true;
  500. }
  501. public function __toString() {
  502. if ($this->types === null) {
  503. return 'mixed';
  504. }
  505. return implode('|', array_map(
  506. function ($type) { return $type->name; },
  507. $this->types)
  508. );
  509. }
  510. }
  511. class ArginfoType {
  512. /** @var SimpleType[] $classTypes */
  513. public $classTypes;
  514. /** @var SimpleType[] $builtinTypes */
  515. private $builtinTypes;
  516. /**
  517. * @param SimpleType[] $classTypes
  518. * @param SimpleType[] $builtinTypes
  519. */
  520. public function __construct(array $classTypes, array $builtinTypes) {
  521. $this->classTypes = $classTypes;
  522. $this->builtinTypes = $builtinTypes;
  523. }
  524. public function hasClassType(): bool {
  525. return !empty($this->classTypes);
  526. }
  527. public function toClassTypeString(): string {
  528. return implode('|', array_map(function(SimpleType $type) {
  529. return $type->toEscapedName();
  530. }, $this->classTypes));
  531. }
  532. public function toTypeMask(): string {
  533. if (empty($this->builtinTypes)) {
  534. return '0';
  535. }
  536. return implode('|', array_map(function(SimpleType $type) {
  537. return $type->toTypeMask();
  538. }, $this->builtinTypes));
  539. }
  540. }
  541. class ArgInfo {
  542. const SEND_BY_VAL = 0;
  543. const SEND_BY_REF = 1;
  544. const SEND_PREFER_REF = 2;
  545. /** @var string */
  546. public $name;
  547. /** @var int */
  548. public $sendBy;
  549. /** @var bool */
  550. public $isVariadic;
  551. /** @var Type|null */
  552. public $type;
  553. /** @var Type|null */
  554. public $phpDocType;
  555. /** @var string|null */
  556. public $defaultValue;
  557. public function __construct(string $name, int $sendBy, bool $isVariadic, ?Type $type, ?Type $phpDocType, ?string $defaultValue) {
  558. $this->name = $name;
  559. $this->sendBy = $sendBy;
  560. $this->isVariadic = $isVariadic;
  561. $this->setTypes($type, $phpDocType);
  562. $this->defaultValue = $defaultValue;
  563. }
  564. public function equals(ArgInfo $other): bool {
  565. return $this->name === $other->name
  566. && $this->sendBy === $other->sendBy
  567. && $this->isVariadic === $other->isVariadic
  568. && Type::equals($this->type, $other->type)
  569. && $this->defaultValue === $other->defaultValue;
  570. }
  571. public function getSendByString(): string {
  572. switch ($this->sendBy) {
  573. case self::SEND_BY_VAL:
  574. return "0";
  575. case self::SEND_BY_REF:
  576. return "1";
  577. case self::SEND_PREFER_REF:
  578. return "ZEND_SEND_PREFER_REF";
  579. }
  580. throw new Exception("Invalid sendBy value");
  581. }
  582. public function getMethodSynopsisType(): Type {
  583. if ($this->type) {
  584. return $this->type;
  585. }
  586. if ($this->phpDocType) {
  587. return $this->phpDocType;
  588. }
  589. throw new Exception("A parameter must have a type");
  590. }
  591. public function hasProperDefaultValue(): bool {
  592. return $this->defaultValue !== null && $this->defaultValue !== "UNKNOWN";
  593. }
  594. public function getDefaultValueAsArginfoString(): string {
  595. if ($this->hasProperDefaultValue()) {
  596. return '"' . addslashes($this->defaultValue) . '"';
  597. }
  598. return "NULL";
  599. }
  600. public function getDefaultValueAsMethodSynopsisString(): ?string {
  601. if ($this->defaultValue === null) {
  602. return null;
  603. }
  604. switch ($this->defaultValue) {
  605. case 'UNKNOWN':
  606. return null;
  607. case 'false':
  608. case 'true':
  609. case 'null':
  610. return "&{$this->defaultValue};";
  611. }
  612. return $this->defaultValue;
  613. }
  614. private function setTypes(?Type $type, ?Type $phpDocType): void
  615. {
  616. if ($phpDocType !== null && Type::equals($type, $phpDocType)) {
  617. throw new Exception('PHPDoc param type "' . $phpDocType->__toString() . '" is unnecessary');
  618. }
  619. $this->type = $type;
  620. $this->phpDocType = $phpDocType;
  621. }
  622. }
  623. class PropertyName {
  624. /** @var Name */
  625. public $class;
  626. /** @var string */
  627. public $property;
  628. public function __construct(Name $class, string $property)
  629. {
  630. $this->class = $class;
  631. $this->property = $property;
  632. }
  633. public function __toString()
  634. {
  635. return $this->class->toString() . "::$" . $this->property;
  636. }
  637. }
  638. interface FunctionOrMethodName {
  639. public function getDeclaration(): string;
  640. public function getArgInfoName(): string;
  641. public function getMethodSynopsisFilename(): string;
  642. public function __toString(): string;
  643. public function isMethod(): bool;
  644. public function isConstructor(): bool;
  645. public function isDestructor(): bool;
  646. }
  647. class FunctionName implements FunctionOrMethodName {
  648. /** @var Name */
  649. private $name;
  650. public function __construct(Name $name) {
  651. $this->name = $name;
  652. }
  653. public function getNamespace(): ?string {
  654. if ($this->name->isQualified()) {
  655. return $this->name->slice(0, -1)->toString();
  656. }
  657. return null;
  658. }
  659. public function getNonNamespacedName(): string {
  660. if ($this->name->isQualified()) {
  661. throw new Exception("Namespaced name not supported here");
  662. }
  663. return $this->name->toString();
  664. }
  665. public function getDeclarationName(): string {
  666. return $this->name->getLast();
  667. }
  668. public function getDeclaration(): string {
  669. return "ZEND_FUNCTION({$this->getDeclarationName()});\n";
  670. }
  671. public function getArgInfoName(): string {
  672. $underscoreName = implode('_', $this->name->parts);
  673. return "arginfo_$underscoreName";
  674. }
  675. public function getMethodSynopsisFilename(): string {
  676. return implode('_', $this->name->parts);
  677. }
  678. public function __toString(): string {
  679. return $this->name->toString();
  680. }
  681. public function isMethod(): bool {
  682. return false;
  683. }
  684. public function isConstructor(): bool {
  685. return false;
  686. }
  687. public function isDestructor(): bool {
  688. return false;
  689. }
  690. }
  691. class MethodName implements FunctionOrMethodName {
  692. /** @var Name */
  693. private $className;
  694. /** @var string */
  695. public $methodName;
  696. public function __construct(Name $className, string $methodName) {
  697. $this->className = $className;
  698. $this->methodName = $methodName;
  699. }
  700. public function getDeclarationClassName(): string {
  701. return implode('_', $this->className->parts);
  702. }
  703. public function getDeclaration(): string {
  704. return "ZEND_METHOD({$this->getDeclarationClassName()}, $this->methodName);\n";
  705. }
  706. public function getArgInfoName(): string {
  707. return "arginfo_class_{$this->getDeclarationClassName()}_{$this->methodName}";
  708. }
  709. public function getMethodSynopsisFilename(): string {
  710. return $this->getDeclarationClassName() . "_{$this->methodName}";
  711. }
  712. public function __toString(): string {
  713. return "$this->className::$this->methodName";
  714. }
  715. public function isMethod(): bool {
  716. return true;
  717. }
  718. public function isConstructor(): bool {
  719. return $this->methodName === "__construct";
  720. }
  721. public function isDestructor(): bool {
  722. return $this->methodName === "__destruct";
  723. }
  724. }
  725. class ReturnInfo {
  726. const REFCOUNT_0 = "0";
  727. const REFCOUNT_1 = "1";
  728. const REFCOUNT_N = "N";
  729. const REFCOUNTS = [
  730. self::REFCOUNT_0,
  731. self::REFCOUNT_1,
  732. self::REFCOUNT_N,
  733. ];
  734. /** @var bool */
  735. public $byRef;
  736. /** @var Type|null */
  737. public $type;
  738. /** @var Type|null */
  739. public $phpDocType;
  740. /** @var bool */
  741. public $tentativeReturnType;
  742. /** @var string */
  743. public $refcount;
  744. public function __construct(bool $byRef, ?Type $type, ?Type $phpDocType, bool $tentativeReturnType, ?string $refcount) {
  745. $this->byRef = $byRef;
  746. $this->setTypes($type, $phpDocType, $tentativeReturnType);
  747. $this->setRefcount($refcount);
  748. }
  749. public function equalsApartFromPhpDocAndRefcount(ReturnInfo $other): bool {
  750. return $this->byRef === $other->byRef
  751. && Type::equals($this->type, $other->type)
  752. && $this->tentativeReturnType === $other->tentativeReturnType;
  753. }
  754. public function getMethodSynopsisType(): ?Type {
  755. return $this->type ?? $this->phpDocType;
  756. }
  757. private function setTypes(?Type $type, ?Type $phpDocType, bool $tentativeReturnType): void
  758. {
  759. if ($phpDocType !== null && Type::equals($type, $phpDocType)) {
  760. throw new Exception('PHPDoc return type "' . $phpDocType->__toString() . '" is unnecessary');
  761. }
  762. $this->type = $type;
  763. $this->phpDocType = $phpDocType;
  764. $this->tentativeReturnType = $tentativeReturnType;
  765. }
  766. private function setRefcount(?string $refcount): void
  767. {
  768. $type = $this->phpDocType ?? $this->type;
  769. $isScalarType = $type !== null && $type->isScalar();
  770. if ($refcount === null) {
  771. $this->refcount = $isScalarType ? self::REFCOUNT_0 : self::REFCOUNT_N;
  772. return;
  773. }
  774. if (!in_array($refcount, ReturnInfo::REFCOUNTS, true)) {
  775. throw new Exception("@refcount must have one of the following values: \"0\", \"1\", \"N\", $refcount given");
  776. }
  777. if ($isScalarType && $refcount !== self::REFCOUNT_0) {
  778. throw new Exception('A scalar return type of "' . $type->__toString() . '" must have a refcount of "' . self::REFCOUNT_0 . '"');
  779. }
  780. if (!$isScalarType && $refcount === self::REFCOUNT_0) {
  781. throw new Exception('A non-scalar return type of "' . $type->__toString() . '" cannot have a refcount of "' . self::REFCOUNT_0 . '"');
  782. }
  783. $this->refcount = $refcount;
  784. }
  785. }
  786. class FuncInfo {
  787. /** @var FunctionOrMethodName */
  788. public $name;
  789. /** @var int */
  790. public $classFlags;
  791. /** @var int */
  792. public $flags;
  793. /** @var string|null */
  794. public $aliasType;
  795. /** @var FunctionName|null */
  796. public $alias;
  797. /** @var bool */
  798. public $isDeprecated;
  799. /** @var bool */
  800. public $verify;
  801. /** @var ArgInfo[] */
  802. public $args;
  803. /** @var ReturnInfo */
  804. public $return;
  805. /** @var int */
  806. public $numRequiredArgs;
  807. /** @var string|null */
  808. public $cond;
  809. /**
  810. * @param ArgInfo[] $args
  811. */
  812. public function __construct(
  813. FunctionOrMethodName $name,
  814. int $classFlags,
  815. int $flags,
  816. ?string $aliasType,
  817. ?FunctionOrMethodName $alias,
  818. bool $isDeprecated,
  819. bool $verify,
  820. array $args,
  821. ReturnInfo $return,
  822. int $numRequiredArgs,
  823. ?string $cond
  824. ) {
  825. $this->name = $name;
  826. $this->classFlags = $classFlags;
  827. $this->flags = $flags;
  828. $this->aliasType = $aliasType;
  829. $this->alias = $alias;
  830. $this->isDeprecated = $isDeprecated;
  831. $this->verify = $verify;
  832. $this->args = $args;
  833. $this->return = $return;
  834. $this->numRequiredArgs = $numRequiredArgs;
  835. $this->cond = $cond;
  836. }
  837. public function isMethod(): bool
  838. {
  839. return $this->name->isMethod();
  840. }
  841. public function isFinalMethod(): bool
  842. {
  843. return ($this->flags & Class_::MODIFIER_FINAL) || ($this->classFlags & Class_::MODIFIER_FINAL);
  844. }
  845. public function isInstanceMethod(): bool
  846. {
  847. return !($this->flags & Class_::MODIFIER_STATIC) && $this->isMethod() && !$this->name->isConstructor();
  848. }
  849. /** @return string[] */
  850. public function getModifierNames(): array
  851. {
  852. if (!$this->isMethod()) {
  853. return [];
  854. }
  855. $result = [];
  856. if ($this->flags & Class_::MODIFIER_FINAL) {
  857. $result[] = "final";
  858. } elseif ($this->flags & Class_::MODIFIER_ABSTRACT && $this->classFlags & ~Class_::MODIFIER_ABSTRACT) {
  859. $result[] = "abstract";
  860. }
  861. if ($this->flags & Class_::MODIFIER_PROTECTED) {
  862. $result[] = "protected";
  863. } elseif ($this->flags & Class_::MODIFIER_PRIVATE) {
  864. $result[] = "private";
  865. } else {
  866. $result[] = "public";
  867. }
  868. if ($this->flags & Class_::MODIFIER_STATIC) {
  869. $result[] = "static";
  870. }
  871. return $result;
  872. }
  873. public function hasParamWithUnknownDefaultValue(): bool
  874. {
  875. foreach ($this->args as $arg) {
  876. if ($arg->defaultValue && !$arg->hasProperDefaultValue()) {
  877. return true;
  878. }
  879. }
  880. return false;
  881. }
  882. public function equalsApartFromNameAndRefcount(FuncInfo $other): bool {
  883. if (count($this->args) !== count($other->args)) {
  884. return false;
  885. }
  886. for ($i = 0; $i < count($this->args); $i++) {
  887. if (!$this->args[$i]->equals($other->args[$i])) {
  888. return false;
  889. }
  890. }
  891. return $this->return->equalsApartFromPhpDocAndRefcount($other->return)
  892. && $this->numRequiredArgs === $other->numRequiredArgs
  893. && $this->cond === $other->cond;
  894. }
  895. public function getArgInfoName(): string {
  896. return $this->name->getArgInfoName();
  897. }
  898. public function getDeclarationKey(): string
  899. {
  900. $name = $this->alias ?? $this->name;
  901. return "$name|$this->cond";
  902. }
  903. public function getDeclaration(): ?string
  904. {
  905. if ($this->flags & Class_::MODIFIER_ABSTRACT) {
  906. return null;
  907. }
  908. $name = $this->alias ?? $this->name;
  909. return $name->getDeclaration();
  910. }
  911. public function getFunctionEntry(): string {
  912. if ($this->name instanceof MethodName) {
  913. if ($this->alias) {
  914. if ($this->alias instanceof MethodName) {
  915. return sprintf(
  916. "\tZEND_MALIAS(%s, %s, %s, %s, %s)\n",
  917. $this->alias->getDeclarationClassName(), $this->name->methodName,
  918. $this->alias->methodName, $this->getArgInfoName(), $this->getFlagsAsArginfoString()
  919. );
  920. } else if ($this->alias instanceof FunctionName) {
  921. return sprintf(
  922. "\tZEND_ME_MAPPING(%s, %s, %s, %s)\n",
  923. $this->name->methodName, $this->alias->getNonNamespacedName(),
  924. $this->getArgInfoName(), $this->getFlagsAsArginfoString()
  925. );
  926. } else {
  927. throw new Error("Cannot happen");
  928. }
  929. } else {
  930. $declarationClassName = $this->name->getDeclarationClassName();
  931. if ($this->flags & Class_::MODIFIER_ABSTRACT) {
  932. return sprintf(
  933. "\tZEND_ABSTRACT_ME_WITH_FLAGS(%s, %s, %s, %s)\n",
  934. $declarationClassName, $this->name->methodName, $this->getArgInfoName(),
  935. $this->getFlagsAsArginfoString()
  936. );
  937. }
  938. return sprintf(
  939. "\tZEND_ME(%s, %s, %s, %s)\n",
  940. $declarationClassName, $this->name->methodName, $this->getArgInfoName(),
  941. $this->getFlagsAsArginfoString()
  942. );
  943. }
  944. } else if ($this->name instanceof FunctionName) {
  945. $namespace = $this->name->getNamespace();
  946. $declarationName = $this->name->getDeclarationName();
  947. if ($this->alias && $this->isDeprecated) {
  948. return sprintf(
  949. "\tZEND_DEP_FALIAS(%s, %s, %s)\n",
  950. $declarationName, $this->alias->getNonNamespacedName(), $this->getArgInfoName()
  951. );
  952. }
  953. if ($this->alias) {
  954. return sprintf(
  955. "\tZEND_FALIAS(%s, %s, %s)\n",
  956. $declarationName, $this->alias->getNonNamespacedName(), $this->getArgInfoName()
  957. );
  958. }
  959. if ($this->isDeprecated) {
  960. return sprintf(
  961. "\tZEND_DEP_FE(%s, %s)\n", $declarationName, $this->getArgInfoName());
  962. }
  963. if ($namespace) {
  964. // Render A\B as "A\\B" in C strings for namespaces
  965. return sprintf(
  966. "\tZEND_NS_FE(\"%s\", %s, %s)\n",
  967. addslashes($namespace), $declarationName, $this->getArgInfoName());
  968. } else {
  969. return sprintf("\tZEND_FE(%s, %s)\n", $declarationName, $this->getArgInfoName());
  970. }
  971. } else {
  972. throw new Error("Cannot happen");
  973. }
  974. }
  975. public function getOptimizerInfo(): ?string {
  976. if ($this->isMethod()) {
  977. return null;
  978. }
  979. if ($this->alias !== null) {
  980. return null;
  981. }
  982. if ($this->return->refcount !== ReturnInfo::REFCOUNT_1 && $this->return->phpDocType === null) {
  983. return null;
  984. }
  985. $type = $this->return->phpDocType ?? $this->return->type;
  986. if ($type === null) {
  987. return null;
  988. }
  989. return " F" . $this->return->refcount . '("' . $this->name->__toString() . '", ' . $type->toOptimizerTypeMask() . "),\n";
  990. }
  991. public function discardInfoForOldPhpVersions(): void {
  992. $this->return->type = null;
  993. foreach ($this->args as $arg) {
  994. $arg->type = null;
  995. $arg->defaultValue = null;
  996. }
  997. }
  998. private function getFlagsAsArginfoString(): string
  999. {
  1000. $flags = "ZEND_ACC_PUBLIC";
  1001. if ($this->flags & Class_::MODIFIER_PROTECTED) {
  1002. $flags = "ZEND_ACC_PROTECTED";
  1003. } elseif ($this->flags & Class_::MODIFIER_PRIVATE) {
  1004. $flags = "ZEND_ACC_PRIVATE";
  1005. }
  1006. if ($this->flags & Class_::MODIFIER_STATIC) {
  1007. $flags .= "|ZEND_ACC_STATIC";
  1008. }
  1009. if ($this->flags & Class_::MODIFIER_FINAL) {
  1010. $flags .= "|ZEND_ACC_FINAL";
  1011. }
  1012. if ($this->flags & Class_::MODIFIER_ABSTRACT) {
  1013. $flags .= "|ZEND_ACC_ABSTRACT";
  1014. }
  1015. if ($this->isDeprecated) {
  1016. $flags .= "|ZEND_ACC_DEPRECATED";
  1017. }
  1018. return $flags;
  1019. }
  1020. /**
  1021. * @param array<string, FuncInfo> $funcMap
  1022. * @param array<string, FuncInfo> $aliasMap
  1023. * @throws Exception
  1024. */
  1025. public function getMethodSynopsisDocument(array $funcMap, array $aliasMap): ?string {
  1026. $doc = new DOMDocument();
  1027. $doc->formatOutput = true;
  1028. $methodSynopsis = $this->getMethodSynopsisElement($funcMap, $aliasMap, $doc);
  1029. if (!$methodSynopsis) {
  1030. return null;
  1031. }
  1032. $doc->appendChild($methodSynopsis);
  1033. return $doc->saveXML();
  1034. }
  1035. /**
  1036. * @param array<string, FuncInfo> $funcMap
  1037. * @param array<string, FuncInfo> $aliasMap
  1038. * @throws Exception
  1039. */
  1040. public function getMethodSynopsisElement(array $funcMap, array $aliasMap, DOMDocument $doc): ?DOMElement {
  1041. if ($this->hasParamWithUnknownDefaultValue()) {
  1042. return null;
  1043. }
  1044. if ($this->name->isConstructor()) {
  1045. $synopsisType = "constructorsynopsis";
  1046. } elseif ($this->name->isDestructor()) {
  1047. $synopsisType = "destructorsynopsis";
  1048. } else {
  1049. $synopsisType = "methodsynopsis";
  1050. }
  1051. $methodSynopsis = $doc->createElement($synopsisType);
  1052. $aliasedFunc = $this->aliasType === "alias" && isset($funcMap[$this->alias->__toString()]) ? $funcMap[$this->alias->__toString()] : null;
  1053. $aliasFunc = $aliasMap[$this->name->__toString()] ?? null;
  1054. if (($this->aliasType === "alias" && $aliasedFunc !== null && $aliasedFunc->isMethod() !== $this->isMethod()) ||
  1055. ($aliasFunc !== null && $aliasFunc->isMethod() !== $this->isMethod())
  1056. ) {
  1057. $role = $doc->createAttribute("role");
  1058. $role->value = $this->isMethod() ? "oop" : "procedural";
  1059. $methodSynopsis->appendChild($role);
  1060. }
  1061. $methodSynopsis->appendChild(new DOMText("\n "));
  1062. foreach ($this->getModifierNames() as $modifierString) {
  1063. $modifierElement = $doc->createElement('modifier', $modifierString);
  1064. $methodSynopsis->appendChild($modifierElement);
  1065. $methodSynopsis->appendChild(new DOMText(" "));
  1066. }
  1067. $returnType = $this->return->getMethodSynopsisType();
  1068. if ($returnType) {
  1069. $methodSynopsis->appendChild($returnType->getTypeForDoc($doc));
  1070. }
  1071. $methodname = $doc->createElement('methodname', $this->name->__toString());
  1072. $methodSynopsis->appendChild($methodname);
  1073. if (empty($this->args)) {
  1074. $methodSynopsis->appendChild(new DOMText("\n "));
  1075. $void = $doc->createElement('void');
  1076. $methodSynopsis->appendChild($void);
  1077. } else {
  1078. foreach ($this->args as $arg) {
  1079. $methodSynopsis->appendChild(new DOMText("\n "));
  1080. $methodparam = $doc->createElement('methodparam');
  1081. if ($arg->defaultValue !== null) {
  1082. $methodparam->setAttribute("choice", "opt");
  1083. }
  1084. if ($arg->isVariadic) {
  1085. $methodparam->setAttribute("rep", "repeat");
  1086. }
  1087. $methodSynopsis->appendChild($methodparam);
  1088. $methodparam->appendChild($arg->getMethodSynopsisType()->getTypeForDoc($doc));
  1089. $parameter = $doc->createElement('parameter', $arg->name);
  1090. if ($arg->sendBy !== ArgInfo::SEND_BY_VAL) {
  1091. $parameter->setAttribute("role", "reference");
  1092. }
  1093. $methodparam->appendChild($parameter);
  1094. $defaultValue = $arg->getDefaultValueAsMethodSynopsisString();
  1095. if ($defaultValue !== null) {
  1096. $initializer = $doc->createElement('initializer');
  1097. if (preg_match('/^[a-zA-Z_][a-zA-Z_0-9]*$/', $defaultValue)) {
  1098. $constant = $doc->createElement('constant', $defaultValue);
  1099. $initializer->appendChild($constant);
  1100. } else {
  1101. $initializer->nodeValue = $defaultValue;
  1102. }
  1103. $methodparam->appendChild($initializer);
  1104. }
  1105. }
  1106. }
  1107. $methodSynopsis->appendChild(new DOMText("\n "));
  1108. return $methodSynopsis;
  1109. }
  1110. public function __clone()
  1111. {
  1112. foreach ($this->args as $key => $argInfo) {
  1113. $this->args[$key] = clone $argInfo;
  1114. }
  1115. $this->return = clone $this->return;
  1116. }
  1117. }
  1118. function initializeZval(string $zvalName, $value): string
  1119. {
  1120. $code = "\tzval $zvalName;\n";
  1121. switch (gettype($value)) {
  1122. case "NULL":
  1123. $code .= "\tZVAL_NULL(&$zvalName);\n";
  1124. break;
  1125. case "boolean":
  1126. $code .= "\tZVAL_BOOL(&$zvalName, " . ((int) $value) . ");\n";
  1127. break;
  1128. case "integer":
  1129. $code .= "\tZVAL_LONG(&$zvalName, $value);\n";
  1130. break;
  1131. case "double":
  1132. $code .= "\tZVAL_DOUBLE(&$zvalName, $value);\n";
  1133. break;
  1134. case "string":
  1135. if ($value === "") {
  1136. $code .= "\tZVAL_EMPTY_STRING(&$zvalName);\n";
  1137. } else {
  1138. $strValue = addslashes($value);
  1139. $code .= "\tzend_string *{$zvalName}_str = zend_string_init(\"$strValue\", sizeof(\"$strValue\") - 1, 1);\n";
  1140. $code .= "\tZVAL_STR(&$zvalName, {$zvalName}_str);\n";
  1141. }
  1142. break;
  1143. case "array":
  1144. if (empty($value)) {
  1145. $code .= "\tZVAL_EMPTY_ARRAY(&$zvalName);\n";
  1146. } else {
  1147. throw new Exception("Unimplemented default value");
  1148. }
  1149. break;
  1150. default:
  1151. throw new Exception("Invalid default value");
  1152. }
  1153. return $code;
  1154. }
  1155. class PropertyInfo
  1156. {
  1157. /** @var PropertyName */
  1158. public $name;
  1159. /** @var int */
  1160. public $flags;
  1161. /** @var Type|null */
  1162. public $type;
  1163. /** @var Type|null */
  1164. public $phpDocType;
  1165. /** @var Expr|null */
  1166. public $defaultValue;
  1167. /** @var string|null */
  1168. public $defaultValueString;
  1169. /** @var bool */
  1170. public $isDocReadonly;
  1171. /** @var string|null */
  1172. public $link;
  1173. public function __construct(
  1174. PropertyName $name,
  1175. int $flags,
  1176. ?Type $type,
  1177. ?Type $phpDocType,
  1178. ?Expr $defaultValue,
  1179. ?string $defaultValueString,
  1180. bool $isDocReadonly,
  1181. ?string $link
  1182. ) {
  1183. $this->name = $name;
  1184. $this->flags = $flags;
  1185. $this->type = $type;
  1186. $this->phpDocType = $phpDocType;
  1187. $this->defaultValue = $defaultValue;
  1188. $this->defaultValueString = $defaultValueString;
  1189. $this->isDocReadonly = $isDocReadonly;
  1190. $this->link = $link;
  1191. }
  1192. public function discardInfoForOldPhpVersions(): void {
  1193. $this->type = null;
  1194. }
  1195. public function getDeclaration(): string {
  1196. $code = "\n";
  1197. $propertyName = $this->name->property;
  1198. $defaultValueConstant = false;
  1199. if ($this->defaultValue === null) {
  1200. $defaultValue = null;
  1201. } else {
  1202. $defaultValue = $this->evaluateDefaultValue($defaultValueConstant);
  1203. }
  1204. if ($defaultValueConstant) {
  1205. echo "Skipping code generation for property $this->name, because it has a constant default value\n";
  1206. return "";
  1207. }
  1208. $typeCode = "";
  1209. if ($this->type) {
  1210. $arginfoType = $this->type->toArginfoType();
  1211. if ($arginfoType->hasClassType()) {
  1212. if (count($arginfoType->classTypes) >= 2) {
  1213. foreach ($arginfoType->classTypes as $classType) {
  1214. $escapedClassName = $classType->toEscapedName();
  1215. $varEscapedClassName = $classType->toVarEscapedName();
  1216. $code .= "\tzend_string *property_{$propertyName}_class_{$varEscapedClassName} = zend_string_init(\"{$escapedClassName}\", sizeof(\"{$escapedClassName}\") - 1, 1);\n";
  1217. }
  1218. $classTypeCount = count($arginfoType->classTypes);
  1219. $code .= "\tzend_type_list *property_{$propertyName}_type_list = malloc(ZEND_TYPE_LIST_SIZE($classTypeCount));\n";
  1220. $code .= "\tproperty_{$propertyName}_type_list->num_types = $classTypeCount;\n";
  1221. foreach ($arginfoType->classTypes as $k => $classType) {
  1222. $escapedClassName = $classType->toEscapedName();
  1223. $code .= "\tproperty_{$propertyName}_type_list->types[$k] = (zend_type) ZEND_TYPE_INIT_CLASS(property_{$propertyName}_class_{$escapedClassName}, 0, 0);\n";
  1224. }
  1225. $typeMaskCode = $this->type->toArginfoType()->toTypeMask();
  1226. $code .= "\tzend_type property_{$propertyName}_type = ZEND_TYPE_INIT_UNION(property_{$propertyName}_type_list, $typeMaskCode);\n";
  1227. $typeCode = "property_{$propertyName}_type";
  1228. } else {
  1229. $escapedClassName = $arginfoType->classTypes[0]->toEscapedName();
  1230. $varEscapedClassName = $arginfoType->classTypes[0]->toVarEscapedName();
  1231. $code .= "\tzend_string *property_{$propertyName}_class_{$varEscapedClassName} = zend_string_init(\"{$escapedClassName}\", sizeof(\"${escapedClassName}\")-1, 1);\n";
  1232. $typeCode = "(zend_type) ZEND_TYPE_INIT_CLASS(property_{$propertyName}_class_{$varEscapedClassName}, 0, " . $arginfoType->toTypeMask() . ")";
  1233. }
  1234. } else {
  1235. $typeCode = "(zend_type) ZEND_TYPE_INIT_MASK(" . $arginfoType->toTypeMask() . ")";
  1236. }
  1237. }
  1238. $zvalName = "property_{$this->name->property}_default_value";
  1239. if ($this->defaultValue === null && $this->type !== null) {
  1240. $code .= "\tzval $zvalName;\n\tZVAL_UNDEF(&$zvalName);\n";
  1241. } else {
  1242. $code .= initializeZval($zvalName, $defaultValue);
  1243. }
  1244. $code .= "\tzend_string *property_{$propertyName}_name = zend_string_init(\"$propertyName\", sizeof(\"$propertyName\") - 1, 1);\n";
  1245. $nameCode = "property_{$propertyName}_name";
  1246. if ($this->type !== null) {
  1247. $code .= "\tzend_declare_typed_property(class_entry, $nameCode, &$zvalName, " . $this->getFlagsAsString() . ", NULL, $typeCode);\n";
  1248. } else {
  1249. $code .= "\tzend_declare_property_ex(class_entry, $nameCode, &$zvalName, " . $this->getFlagsAsString() . ", NULL);\n";
  1250. }
  1251. $code .= "\tzend_string_release(property_{$propertyName}_name);\n";
  1252. return $code;
  1253. }
  1254. private function getFlagsAsString(): string
  1255. {
  1256. $flags = "ZEND_ACC_PUBLIC";
  1257. if ($this->flags & Class_::MODIFIER_PROTECTED) {
  1258. $flags = "ZEND_ACC_PROTECTED";
  1259. } elseif ($this->flags & Class_::MODIFIER_PRIVATE) {
  1260. $flags = "ZEND_ACC_PRIVATE";
  1261. }
  1262. if ($this->flags & Class_::MODIFIER_STATIC) {
  1263. $flags .= "|ZEND_ACC_STATIC";
  1264. }
  1265. if ($this->flags & Class_::MODIFIER_READONLY) {
  1266. $flags .= "|ZEND_ACC_READONLY";
  1267. }
  1268. return $flags;
  1269. }
  1270. public function getFieldSynopsisElement(DOMDocument $doc): DOMElement
  1271. {
  1272. $fieldsynopsisElement = $doc->createElement("fieldsynopsis");
  1273. if ($this->flags & Class_::MODIFIER_PUBLIC) {
  1274. $fieldsynopsisElement->appendChild(new DOMText("\n "));
  1275. $fieldsynopsisElement->appendChild($doc->createElement("modifier", "public"));
  1276. } elseif ($this->flags & Class_::MODIFIER_PROTECTED) {
  1277. $fieldsynopsisElement->appendChild(new DOMText("\n "));
  1278. $fieldsynopsisElement->appendChild($doc->createElement("modifier", "protected"));
  1279. } elseif ($this->flags & Class_::MODIFIER_PRIVATE) {
  1280. $fieldsynopsisElement->appendChild(new DOMText("\n "));
  1281. $fieldsynopsisElement->appendChild($doc->createElement("modifier", "private"));
  1282. }
  1283. if ($this->flags & Class_::MODIFIER_STATIC) {
  1284. $fieldsynopsisElement->appendChild(new DOMText("\n "));
  1285. $fieldsynopsisElement->appendChild($doc->createElement("modifier", "static"));
  1286. } elseif ($this->flags & Class_::MODIFIER_READONLY || $this->isDocReadonly) {
  1287. $fieldsynopsisElement->appendChild(new DOMText("\n "));
  1288. $fieldsynopsisElement->appendChild($doc->createElement("modifier", "readonly"));
  1289. }
  1290. $fieldsynopsisElement->appendChild(new DOMText("\n "));
  1291. $fieldsynopsisElement->appendChild($this->getFieldSynopsisType()->getTypeForDoc($doc));
  1292. $className = str_replace(["\\", "_"], ["-", "-"], $this->name->class->toLowerString());
  1293. $varnameElement = $doc->createElement("varname", $this->name->property);
  1294. if ($this->link) {
  1295. $varnameElement->setAttribute("linkend", $this->link);
  1296. } else {
  1297. $varnameElement->setAttribute("linkend", "$className.props." . strtolower(str_replace("_", "-", $this->name->property)));
  1298. }
  1299. $fieldsynopsisElement->appendChild(new DOMText("\n "));
  1300. $fieldsynopsisElement->appendChild($varnameElement);
  1301. if ($this->defaultValueString) {
  1302. $fieldsynopsisElement->appendChild(new DOMText("\n "));
  1303. $initializerElement = $doc->createElement("initializer", $this->defaultValueString);
  1304. $fieldsynopsisElement->appendChild($initializerElement);
  1305. }
  1306. $fieldsynopsisElement->appendChild(new DOMText("\n "));
  1307. return $fieldsynopsisElement;
  1308. }
  1309. private function getFieldSynopsisType(): Type {
  1310. if ($this->phpDocType) {
  1311. return $this->phpDocType;
  1312. }
  1313. if ($this->type) {
  1314. return $this->type;
  1315. }
  1316. throw new Exception("A property must have a type");
  1317. }
  1318. /** @return mixed */
  1319. private function evaluateDefaultValue(bool &$defaultValueConstant)
  1320. {
  1321. $evaluator = new ConstExprEvaluator(
  1322. function (Expr $expr) use (&$defaultValueConstant) {
  1323. if ($expr instanceof Expr\ConstFetch) {
  1324. $defaultValueConstant = true;
  1325. return null;
  1326. }
  1327. throw new Exception("Property $this->name has an unsupported default value");
  1328. }
  1329. );
  1330. return $evaluator->evaluateDirectly($this->defaultValue);
  1331. }
  1332. public function __clone()
  1333. {
  1334. if ($this->type) {
  1335. $this->type = clone $this->type;
  1336. }
  1337. }
  1338. }
  1339. class EnumCaseInfo {
  1340. /** @var string */
  1341. public $name;
  1342. /** @var Expr|null */
  1343. public $value;
  1344. public function __construct(string $name, ?Expr $value) {
  1345. $this->name = $name;
  1346. $this->value = $value;
  1347. }
  1348. public function getDeclaration(): string {
  1349. $escapedName = addslashes($this->name);
  1350. if ($this->value === null) {
  1351. $code = "\n\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", NULL);\n";
  1352. } else {
  1353. $evaluator = new ConstExprEvaluator(function (Expr $expr) {
  1354. throw new Exception("Enum case $this->name has an unsupported value");
  1355. });
  1356. $zvalName = "enum_case_{$escapedName}_value";
  1357. $code = "\n" . initializeZval($zvalName, $evaluator->evaluateDirectly($this->value));
  1358. $code .= "\tzend_enum_add_case_cstr(class_entry, \"$escapedName\", &$zvalName);\n";
  1359. }
  1360. return $code;
  1361. }
  1362. }
  1363. class ClassInfo {
  1364. /** @var Name */
  1365. public $name;
  1366. /** @var int */
  1367. public $flags;
  1368. /** @var string */
  1369. public $type;
  1370. /** @var string|null */
  1371. public $alias;
  1372. /** @var SimpleType|null */
  1373. public $enumBackingType;
  1374. /** @var bool */
  1375. public $isDeprecated;
  1376. /** @var bool */
  1377. public $isStrictProperties;
  1378. /** @var bool */
  1379. public $isNotSerializable;
  1380. /** @var Name[] */
  1381. public $extends;
  1382. /** @var Name[] */
  1383. public $implements;
  1384. /** @var PropertyInfo[] */
  1385. public $propertyInfos;
  1386. /** @var FuncInfo[] */
  1387. public $funcInfos;
  1388. /** @var EnumCaseInfo[] */
  1389. public $enumCaseInfos;
  1390. /**
  1391. * @param Name[] $extends
  1392. * @param Name[] $implements
  1393. * @param PropertyInfo[] $propertyInfos
  1394. * @param FuncInfo[] $funcInfos
  1395. * @param EnumCaseInfo[] $enumCaseInfos
  1396. */
  1397. public function __construct(
  1398. Name $name,
  1399. int $flags,
  1400. string $type,
  1401. ?string $alias,
  1402. ?SimpleType $enumBackingType,
  1403. bool $isDeprecated,
  1404. bool $isStrictProperties,
  1405. bool $isNotSerializable,
  1406. array $extends,
  1407. array $implements,
  1408. array $propertyInfos,
  1409. array $funcInfos,
  1410. array $enumCaseInfos
  1411. ) {
  1412. $this->name = $name;
  1413. $this->flags = $flags;
  1414. $this->type = $type;
  1415. $this->alias = $alias;
  1416. $this->enumBackingType = $enumBackingType;
  1417. $this->isDeprecated = $isDeprecated;
  1418. $this->isStrictProperties = $isStrictProperties;
  1419. $this->isNotSerializable = $isNotSerializable;
  1420. $this->extends = $extends;
  1421. $this->implements = $implements;
  1422. $this->propertyInfos = $propertyInfos;
  1423. $this->funcInfos = $funcInfos;
  1424. $this->enumCaseInfos = $enumCaseInfos;
  1425. }
  1426. public function getRegistration(): string
  1427. {
  1428. $params = [];
  1429. foreach ($this->extends as $extends) {
  1430. $params[] = "zend_class_entry *class_entry_" . implode("_", $extends->parts);
  1431. }
  1432. foreach ($this->implements as $implements) {
  1433. $params[] = "zend_class_entry *class_entry_" . implode("_", $implements->parts);
  1434. }
  1435. $escapedName = implode("_", $this->name->parts);
  1436. $code = "static zend_class_entry *register_class_$escapedName(" . (empty($params) ? "void" : implode(", ", $params)) . ")\n";
  1437. $code .= "{\n";
  1438. if ($this->type == "enum") {
  1439. $name = addslashes((string) $this->name);
  1440. $backingType = $this->enumBackingType
  1441. ? $this->enumBackingType->toTypeCode() : "IS_UNDEF";
  1442. $code .= "\tzend_class_entry *class_entry = zend_register_internal_enum(\"$name\", $backingType, class_{$escapedName}_methods);\n";
  1443. } else {
  1444. $code .= "\tzend_class_entry ce, *class_entry;\n\n";
  1445. if (count($this->name->parts) > 1) {
  1446. $className = $this->name->getLast();
  1447. $namespace = addslashes((string) $this->name->slice(0, -1));
  1448. $code .= "\tINIT_NS_CLASS_ENTRY(ce, \"$namespace\", \"$className\", class_{$escapedName}_methods);\n";
  1449. } else {
  1450. $code .= "\tINIT_CLASS_ENTRY(ce, \"$this->name\", class_{$escapedName}_methods);\n";
  1451. }
  1452. if ($this->type === "class" || $this->type === "trait") {
  1453. $code .= "\tclass_entry = zend_register_internal_class_ex(&ce, " . (isset($this->extends[0]) ? "class_entry_" . str_replace("\\", "_", $this->extends[0]->toString()) : "NULL") . ");\n";
  1454. } else {
  1455. $code .= "\tclass_entry = zend_register_internal_interface(&ce);\n";
  1456. }
  1457. }
  1458. if ($this->getFlagsAsString()) {
  1459. $code .= "\tclass_entry->ce_flags |= " . $this->getFlagsAsString() . ";\n";
  1460. }
  1461. $implements = array_map(
  1462. function (Name $item) {
  1463. return "class_entry_" . implode("_", $item->parts);
  1464. },
  1465. $this->type === "interface" ? $this->extends : $this->implements
  1466. );
  1467. if (!empty($implements)) {
  1468. $code .= "\tzend_class_implements(class_entry, " . count($implements) . ", " . implode(", ", $implements) . ");\n";
  1469. }
  1470. if ($this->alias) {
  1471. $code .= "\tzend_register_class_alias(\"" . str_replace("\\", "\\\\", $this->alias) . "\", class_entry);\n";
  1472. }
  1473. foreach ($this->enumCaseInfos as $enumCase) {
  1474. $code .= $enumCase->getDeclaration();
  1475. }
  1476. foreach ($this->propertyInfos as $property) {
  1477. $code .= $property->getDeclaration();
  1478. }
  1479. $code .= "\n\treturn class_entry;\n";
  1480. $code .= "}\n";
  1481. return $code;
  1482. }
  1483. private function getFlagsAsString(): string
  1484. {
  1485. $flags = [];
  1486. if ($this->type === "trait") {
  1487. $flags[] = "ZEND_ACC_TRAIT";
  1488. }
  1489. if ($this->flags & Class_::MODIFIER_FINAL) {
  1490. $flags[] = "ZEND_ACC_FINAL";
  1491. }
  1492. if ($this->flags & Class_::MODIFIER_ABSTRACT) {
  1493. $flags[] = "ZEND_ACC_ABSTRACT";
  1494. }
  1495. if ($this->isDeprecated) {
  1496. $flags[] = "ZEND_ACC_DEPRECATED";
  1497. }
  1498. if ($this->isStrictProperties) {
  1499. $flags[] = "ZEND_ACC_NO_DYNAMIC_PROPERTIES";
  1500. }
  1501. if ($this->isNotSerializable) {
  1502. $flags[] = "ZEND_ACC_NOT_SERIALIZABLE";
  1503. }
  1504. return implode("|", $flags);
  1505. }
  1506. /**
  1507. * @param array<string, ClassInfo> $classMap
  1508. */
  1509. public function getClassSynopsisDocument(array $classMap): ?string {
  1510. $doc = new DOMDocument();
  1511. $doc->formatOutput = true;
  1512. $classSynopsis = $this->getClassSynopsisElement($doc, $classMap);
  1513. if (!$classSynopsis) {
  1514. return null;
  1515. }
  1516. $doc->appendChild($classSynopsis);
  1517. return $doc->saveXML();
  1518. }
  1519. /**
  1520. * @param ClassInfo[] $classMap
  1521. */
  1522. public function getClassSynopsisElement(DOMDocument $doc, array $classMap): ?DOMElement {
  1523. $classSynopsis = $doc->createElement("classsynopsis");
  1524. $classSynopsis->appendChild(new DOMText("\n "));
  1525. $ooElement = self::createOoElement($doc, $this, true, false, false, 4);
  1526. if (!$ooElement) {
  1527. return null;
  1528. }
  1529. $classSynopsis->appendChild($ooElement);
  1530. $classSynopsis->appendChild(new DOMText("\n\n "));
  1531. $classSynopsisInfo = $doc->createElement("classsynopsisinfo");
  1532. $classSynopsisInfo->appendChild(new DOMText("\n "));
  1533. $ooElement = self::createOoElement($doc, $this, false, true, false, 5);
  1534. if (!$ooElement) {
  1535. return null;
  1536. }
  1537. $classSynopsisInfo->appendChild($ooElement);
  1538. $classSynopsis->appendChild($classSynopsisInfo);
  1539. foreach ($this->extends as $k => $parent) {
  1540. $parentInfo = $classMap[$parent->toString()] ?? null;
  1541. if ($parentInfo === null) {
  1542. throw new Exception("Missing parent class " . $parent->toString());
  1543. }
  1544. $ooElement = self::createOoElement(
  1545. $doc,
  1546. $parentInfo,
  1547. $this->type === "interface",
  1548. false,
  1549. $k === 0,
  1550. 5
  1551. );
  1552. if (!$ooElement) {
  1553. return null;
  1554. }
  1555. $classSynopsisInfo->appendChild(new DOMText("\n\n "));
  1556. $classSynopsisInfo->appendChild($ooElement);
  1557. }
  1558. foreach ($this->implements as $interface) {
  1559. $interfaceInfo = $classMap[$interface->toString()] ?? null;
  1560. if (!$interfaceInfo) {
  1561. throw new Exception("Missing implemented interface " . $interface->toString());
  1562. }
  1563. $ooElement = self::createOoElement($doc, $interfaceInfo, false, false, false, 5);
  1564. if (!$ooElement) {
  1565. return null;
  1566. }
  1567. $classSynopsisInfo->appendChild(new DOMText("\n\n "));
  1568. $classSynopsisInfo->appendChild($ooElement);
  1569. }
  1570. $classSynopsisInfo->appendChild(new DOMText("\n "));
  1571. /** @var Name[] $parentsWithInheritedProperties */
  1572. $parentsWithInheritedProperties = [];
  1573. /** @var Name[] $parentsWithInheritedMethods */
  1574. $parentsWithInheritedMethods = [];
  1575. $this->collectInheritedMembers($parentsWithInheritedProperties, $parentsWithInheritedMethods, $classMap);
  1576. if (!empty($this->propertyInfos)) {
  1577. $classSynopsis->appendChild(new DOMText("\n\n "));
  1578. $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Properties;");
  1579. $classSynopsisInfo->setAttribute("role", "comment");
  1580. $classSynopsis->appendChild($classSynopsisInfo);
  1581. foreach ($this->propertyInfos as $propertyInfo) {
  1582. $classSynopsis->appendChild(new DOMText("\n "));
  1583. $fieldSynopsisElement = $propertyInfo->getFieldSynopsisElement($doc);
  1584. $classSynopsis->appendChild($fieldSynopsisElement);
  1585. }
  1586. }
  1587. if (!empty($parentsWithInheritedProperties)) {
  1588. $classSynopsis->appendChild(new DOMText("\n\n "));
  1589. $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&InheritedProperties;");
  1590. $classSynopsisInfo->setAttribute("role", "comment");
  1591. $classSynopsis->appendChild($classSynopsisInfo);
  1592. foreach ($parentsWithInheritedProperties as $parent) {
  1593. $classSynopsis->appendChild(new DOMText("\n "));
  1594. $parentReference = self::getClassSynopsisReference($parent);
  1595. $includeElement = $this->createIncludeElement(
  1596. $doc,
  1597. "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$parentReference')/db:partintro/db:section/db:classsynopsis/db:fieldsynopsis[preceding-sibling::db:classsynopsisinfo[1][@role='comment' and text()='&Properties;']]))"
  1598. );
  1599. $classSynopsis->appendChild($includeElement);
  1600. }
  1601. }
  1602. if (!empty($this->funcInfos)) {
  1603. $classSynopsis->appendChild(new DOMText("\n\n "));
  1604. $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&Methods;");
  1605. $classSynopsisInfo->setAttribute("role", "comment");
  1606. $classSynopsis->appendChild($classSynopsisInfo);
  1607. $classReference = self::getClassSynopsisReference($this->name);
  1608. if ($this->hasConstructor()) {
  1609. $classSynopsis->appendChild(new DOMText("\n "));
  1610. $includeElement = $this->createIncludeElement(
  1611. $doc,
  1612. "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:constructorsynopsis[not(@role='procedural')])"
  1613. );
  1614. $classSynopsis->appendChild($includeElement);
  1615. }
  1616. if ($this->hasMethods()) {
  1617. $classSynopsis->appendChild(new DOMText("\n "));
  1618. $includeElement = $this->createIncludeElement(
  1619. $doc,
  1620. "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[not(@role='procedural')])"
  1621. );
  1622. $classSynopsis->appendChild($includeElement);
  1623. }
  1624. if ($this->hasDestructor()) {
  1625. $classSynopsis->appendChild(new DOMText("\n "));
  1626. $includeElement = $this->createIncludeElement(
  1627. $doc,
  1628. "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$classReference')/db:refentry/db:refsect1[@role='description']/descendant::db:destructorsynopsis[not(@role='procedural')])"
  1629. );
  1630. $classSynopsis->appendChild($includeElement);
  1631. }
  1632. }
  1633. if (!empty($parentsWithInheritedMethods)) {
  1634. $classSynopsis->appendChild(new DOMText("\n\n "));
  1635. $classSynopsisInfo = $doc->createElement("classsynopsisinfo", "&InheritedMethods;");
  1636. $classSynopsisInfo->setAttribute("role", "comment");
  1637. $classSynopsis->appendChild($classSynopsisInfo);
  1638. foreach ($parentsWithInheritedMethods as $parent) {
  1639. $classSynopsis->appendChild(new DOMText("\n "));
  1640. $parentReference = self::getClassSynopsisReference($parent);
  1641. $includeElement = $this->createIncludeElement(
  1642. $doc,
  1643. "xmlns(db=http://docbook.org/ns/docbook) xpointer(id('$parentReference')/db:refentry/db:refsect1[@role='description']/descendant::db:methodsynopsis[not(@role='procedural')])"
  1644. );
  1645. $classSynopsis->appendChild($includeElement);
  1646. }
  1647. }
  1648. $classSynopsis->appendChild(new DOMText("\n "));
  1649. return $classSynopsis;
  1650. }
  1651. private static function createOoElement(
  1652. DOMDocument $doc,
  1653. ClassInfo $classInfo,
  1654. bool $overrideToClass,
  1655. bool $withModifiers,
  1656. bool $isExtends,
  1657. int $indentationLevel
  1658. ): ?DOMElement {
  1659. $indentation = str_repeat(" ", $indentationLevel);
  1660. if ($classInfo->type !== "class" && $classInfo->type !== "interface") {
  1661. echo "Class synopsis generation is not implemented for " . $classInfo->type . "\n";
  1662. return null;
  1663. }
  1664. $type = $overrideToClass ? "class" : $classInfo->type;
  1665. $ooElement = $doc->createElement("oo$type");
  1666. $ooElement->appendChild(new DOMText("\n$indentation "));
  1667. if ($isExtends) {
  1668. $ooElement->appendChild($doc->createElement('modifier', 'extends'));
  1669. $ooElement->appendChild(new DOMText("\n$indentation "));
  1670. } elseif ($withModifiers) {
  1671. if ($classInfo->flags & Class_::MODIFIER_FINAL) {
  1672. $ooElement->appendChild($doc->createElement('modifier', 'final'));
  1673. $ooElement->appendChild(new DOMText("\n$indentation "));
  1674. }
  1675. if ($classInfo->flags & Class_::MODIFIER_ABSTRACT) {
  1676. $ooElement->appendChild($doc->createElement('modifier', 'abstract'));
  1677. $ooElement->appendChild(new DOMText("\n$indentation "));
  1678. }
  1679. }
  1680. $nameElement = $doc->createElement("{$type}name", $classInfo->name->toString());
  1681. $ooElement->appendChild($nameElement);
  1682. $ooElement->appendChild(new DOMText("\n$indentation"));
  1683. return $ooElement;
  1684. }
  1685. public static function getClassSynopsisFilename(Name $name): string {
  1686. return strtolower(str_replace("_", "-", implode('-', $name->parts)));
  1687. }
  1688. public static function getClassSynopsisReference(Name $name): string {
  1689. return "class." . self::getClassSynopsisFilename($name);
  1690. }
  1691. /**
  1692. * @param Name[] $parentsWithInheritedProperties
  1693. * @param Name[] $parentsWithInheritedMethods
  1694. * @param array<string, ClassInfo> $classMap
  1695. */
  1696. private function collectInheritedMembers(array &$parentsWithInheritedProperties, array &$parentsWithInheritedMethods, array $classMap): void
  1697. {
  1698. foreach ($this->extends as $parent) {
  1699. $parentInfo = $classMap[$parent->toString()] ?? null;
  1700. if (!$parentInfo) {
  1701. throw new Exception("Missing parent class " . $parent->toString());
  1702. }
  1703. if (!empty($parentInfo->propertyInfos) && !isset($parentsWithInheritedProperties[$parent->toString()])) {
  1704. $parentsWithInheritedProperties[$parent->toString()] = $parent;
  1705. }
  1706. if (!isset($parentsWithInheritedMethods[$parent->toString()]) && $parentInfo->hasMethods()) {
  1707. $parentsWithInheritedMethods[$parent->toString()] = $parent;
  1708. }
  1709. $parentInfo->collectInheritedMembers($parentsWithInheritedProperties, $parentsWithInheritedMethods, $classMap);
  1710. }
  1711. }
  1712. private function hasConstructor(): bool
  1713. {
  1714. foreach ($this->funcInfos as $funcInfo) {
  1715. if ($funcInfo->name->isConstructor()) {
  1716. return true;
  1717. }
  1718. }
  1719. return false;
  1720. }
  1721. private function hasDestructor(): bool
  1722. {
  1723. foreach ($this->funcInfos as $funcInfo) {
  1724. if ($funcInfo->name->isDestructor()) {
  1725. return true;
  1726. }
  1727. }
  1728. return false;
  1729. }
  1730. private function hasMethods(): bool
  1731. {
  1732. foreach ($this->funcInfos as $funcInfo) {
  1733. if (!$funcInfo->name->isConstructor() && !$funcInfo->name->isDestructor()) {
  1734. return true;
  1735. }
  1736. }
  1737. return false;
  1738. }
  1739. private function createIncludeElement(DOMDocument $doc, string $query): DOMElement
  1740. {
  1741. $includeElement = $doc->createElement("xi:include");
  1742. $attr = $doc->createAttribute("xpointer");
  1743. $attr->value = $query;
  1744. $includeElement->appendChild($attr);
  1745. $fallbackElement = $doc->createElement("xi:fallback");
  1746. $includeElement->appendChild(new DOMText("\n "));
  1747. $includeElement->appendChild($fallbackElement);
  1748. $includeElement->appendChild(new DOMText("\n "));
  1749. return $includeElement;
  1750. }
  1751. public function __clone()
  1752. {
  1753. foreach ($this->propertyInfos as $key => $propertyInfo) {
  1754. $this->propertyInfos[$key] = clone $propertyInfo;
  1755. }
  1756. foreach ($this->funcInfos as $key => $funcInfo) {
  1757. $this->funcInfos[$key] = clone $funcInfo;
  1758. }
  1759. }
  1760. }
  1761. class FileInfo {
  1762. /** @var FuncInfo[] */
  1763. public $funcInfos = [];
  1764. /** @var ClassInfo[] */
  1765. public $classInfos = [];
  1766. /** @var bool */
  1767. public $generateFunctionEntries = false;
  1768. /** @var string */
  1769. public $declarationPrefix = "";
  1770. /** @var bool */
  1771. public $generateLegacyArginfo = false;
  1772. /** @var bool */
  1773. public $generateClassEntries = false;
  1774. /**
  1775. * @return iterable<FuncInfo>
  1776. */
  1777. public function getAllFuncInfos(): iterable {
  1778. yield from $this->funcInfos;
  1779. foreach ($this->classInfos as $classInfo) {
  1780. yield from $classInfo->funcInfos;
  1781. }
  1782. }
  1783. /**
  1784. * @return iterable<PropertyInfo>
  1785. */
  1786. public function getAllPropertyInfos(): iterable {
  1787. foreach ($this->classInfos as $classInfo) {
  1788. yield from $classInfo->propertyInfos;
  1789. }
  1790. }
  1791. public function __clone()
  1792. {
  1793. foreach ($this->funcInfos as $key => $funcInfo) {
  1794. $this->funcInfos[$key] = clone $funcInfo;
  1795. }
  1796. foreach ($this->classInfos as $key => $classInfo) {
  1797. $this->classInfos[$key] = clone $classInfo;
  1798. }
  1799. }
  1800. }
  1801. class DocCommentTag {
  1802. /** @var string */
  1803. public $name;
  1804. /** @var string|null */
  1805. public $value;
  1806. public function __construct(string $name, ?string $value) {
  1807. $this->name = $name;
  1808. $this->value = $value;
  1809. }
  1810. public function getValue(): string {
  1811. if ($this->value === null) {
  1812. throw new Exception("@$this->name does not have a value");
  1813. }
  1814. return $this->value;
  1815. }
  1816. public function getType(): string {
  1817. $value = $this->getValue();
  1818. $matches = [];
  1819. if ($this->name === "param") {
  1820. preg_match('/^\s*([\w\|\\\\\[\]<>, ]+)\s*\$\w+.*$/', $value, $matches);
  1821. } elseif ($this->name === "return" || $this->name === "var") {
  1822. preg_match('/^\s*([\w\|\\\\\[\]<>, ]+)/', $value, $matches);
  1823. }
  1824. if (!isset($matches[1])) {
  1825. throw new Exception("@$this->name doesn't contain a type or has an invalid format \"$value\"");
  1826. }
  1827. return trim($matches[1]);
  1828. }
  1829. public function getVariableName(): string {
  1830. $value = $this->value;
  1831. if ($value === null || strlen($value) === 0) {
  1832. throw new Exception("@$this->name doesn't have any value");
  1833. }
  1834. $matches = [];
  1835. if ($this->name === "param") {
  1836. preg_match('/^\s*[\w\|\\\\\[\]]+\s*\$(\w+).*$/', $value, $matches);
  1837. } elseif ($this->name === "prefer-ref") {
  1838. preg_match('/^\s*\$(\w+).*$/', $value, $matches);
  1839. }
  1840. if (!isset($matches[1])) {
  1841. throw new Exception("@$this->name doesn't contain a variable name or has an invalid format \"$value\"");
  1842. }
  1843. return $matches[1];
  1844. }
  1845. }
  1846. /** @return DocCommentTag[] */
  1847. function parseDocComment(DocComment $comment): array {
  1848. $commentText = substr($comment->getText(), 2, -2);
  1849. $tags = [];
  1850. foreach (explode("\n", $commentText) as $commentLine) {
  1851. $regex = '/^\*\s*@([a-z-]+)(?:\s+(.+))?$/';
  1852. if (preg_match($regex, trim($commentLine), $matches)) {
  1853. $tags[] = new DocCommentTag($matches[1], $matches[2] ?? null);
  1854. }
  1855. }
  1856. return $tags;
  1857. }
  1858. function parseFunctionLike(
  1859. PrettyPrinterAbstract $prettyPrinter,
  1860. FunctionOrMethodName $name,
  1861. int $classFlags,
  1862. int $flags,
  1863. Node\FunctionLike $func,
  1864. ?string $cond
  1865. ): FuncInfo {
  1866. try {
  1867. $comment = $func->getDocComment();
  1868. $paramMeta = [];
  1869. $aliasType = null;
  1870. $alias = null;
  1871. $isDeprecated = false;
  1872. $verify = true;
  1873. $docReturnType = null;
  1874. $tentativeReturnType = false;
  1875. $docParamTypes = [];
  1876. $refcount = null;
  1877. if ($comment) {
  1878. $tags = parseDocComment($comment);
  1879. foreach ($tags as $tag) {
  1880. if ($tag->name === 'prefer-ref') {
  1881. $varName = $tag->getVariableName();
  1882. if (!isset($paramMeta[$varName])) {
  1883. $paramMeta[$varName] = [];
  1884. }
  1885. $paramMeta[$varName]['preferRef'] = true;
  1886. } else if ($tag->name === 'alias' || $tag->name === 'implementation-alias') {
  1887. $aliasType = $tag->name;
  1888. $aliasParts = explode("::", $tag->getValue());
  1889. if (count($aliasParts) === 1) {
  1890. $alias = new FunctionName(new Name($aliasParts[0]));
  1891. } else {
  1892. $alias = new MethodName(new Name($aliasParts[0]), $aliasParts[1]);
  1893. }
  1894. } else if ($tag->name === 'deprecated') {
  1895. $isDeprecated = true;
  1896. } else if ($tag->name === 'no-verify') {
  1897. $verify = false;
  1898. } else if ($tag->name === 'tentative-return-type') {
  1899. $tentativeReturnType = true;
  1900. } else if ($tag->name === 'return') {
  1901. $docReturnType = $tag->getType();
  1902. } else if ($tag->name === 'param') {
  1903. $docParamTypes[$tag->getVariableName()] = $tag->getType();
  1904. } else if ($tag->name === 'refcount') {
  1905. $refcount = $tag->getValue();
  1906. }
  1907. }
  1908. }
  1909. $varNameSet = [];
  1910. $args = [];
  1911. $numRequiredArgs = 0;
  1912. $foundVariadic = false;
  1913. foreach ($func->getParams() as $i => $param) {
  1914. $varName = $param->var->name;
  1915. $preferRef = !empty($paramMeta[$varName]['preferRef']);
  1916. unset($paramMeta[$varName]);
  1917. if (isset($varNameSet[$varName])) {
  1918. throw new Exception("Duplicate parameter name $varName");
  1919. }
  1920. $varNameSet[$varName] = true;
  1921. if ($preferRef) {
  1922. $sendBy = ArgInfo::SEND_PREFER_REF;
  1923. } else if ($param->byRef) {
  1924. $sendBy = ArgInfo::SEND_BY_REF;
  1925. } else {
  1926. $sendBy = ArgInfo::SEND_BY_VAL;
  1927. }
  1928. if ($foundVariadic) {
  1929. throw new Exception("Only the last parameter can be variadic");
  1930. }
  1931. $type = $param->type ? Type::fromNode($param->type) : null;
  1932. if ($type === null && !isset($docParamTypes[$varName])) {
  1933. throw new Exception("Missing parameter type");
  1934. }
  1935. if ($param->default instanceof Expr\ConstFetch &&
  1936. $param->default->name->toLowerString() === "null" &&
  1937. $type && !$type->isNullable()
  1938. ) {
  1939. $simpleType = $type->tryToSimpleType();
  1940. if ($simpleType === null) {
  1941. throw new Exception("Parameter $varName has null default, but is not nullable");
  1942. }
  1943. }
  1944. if ($param->default instanceof Expr\ClassConstFetch && $param->default->class->toLowerString() === "self") {
  1945. throw new Exception('The exact class name must be used instead of "self"');
  1946. }
  1947. $foundVariadic = $param->variadic;
  1948. $args[] = new ArgInfo(
  1949. $varName,
  1950. $sendBy,
  1951. $param->variadic,
  1952. $type,
  1953. isset($docParamTypes[$varName]) ? Type::fromString($docParamTypes[$varName]) : null,
  1954. $param->default ? $prettyPrinter->prettyPrintExpr($param->default) : null
  1955. );
  1956. if (!$param->default && !$param->variadic) {
  1957. $numRequiredArgs = $i + 1;
  1958. }
  1959. }
  1960. foreach (array_keys($paramMeta) as $var) {
  1961. throw new Exception("Found metadata for invalid param $var");
  1962. }
  1963. $returnType = $func->getReturnType();
  1964. if ($returnType === null && $docReturnType === null && !$name->isConstructor() && !$name->isDestructor()) {
  1965. throw new Exception("Missing return type");
  1966. }
  1967. $return = new ReturnInfo(
  1968. $func->returnsByRef(),
  1969. $returnType ? Type::fromNode($returnType) : null,
  1970. $docReturnType ? Type::fromString($docReturnType) : null,
  1971. $tentativeReturnType,
  1972. $refcount
  1973. );
  1974. return new FuncInfo(
  1975. $name,
  1976. $classFlags,
  1977. $flags,
  1978. $aliasType,
  1979. $alias,
  1980. $isDeprecated,
  1981. $verify,
  1982. $args,
  1983. $return,
  1984. $numRequiredArgs,
  1985. $cond
  1986. );
  1987. } catch (Exception $e) {
  1988. throw new Exception($name . "(): " .$e->getMessage());
  1989. }
  1990. }
  1991. function parseProperty(
  1992. Name $class,
  1993. int $flags,
  1994. Stmt\PropertyProperty $property,
  1995. ?Node $type,
  1996. ?DocComment $comment,
  1997. PrettyPrinterAbstract $prettyPrinter
  1998. ): PropertyInfo {
  1999. $phpDocType = null;
  2000. $isDocReadonly = false;
  2001. $link = null;
  2002. if ($comment) {
  2003. $tags = parseDocComment($comment);
  2004. foreach ($tags as $tag) {
  2005. if ($tag->name === 'var') {
  2006. $phpDocType = $tag->getType();
  2007. } elseif ($tag->name === 'readonly') {
  2008. $isDocReadonly = true;
  2009. } elseif ($tag->name === 'link') {
  2010. $link = $tag->value;
  2011. }
  2012. }
  2013. }
  2014. $propertyType = $type ? Type::fromNode($type) : null;
  2015. if ($propertyType === null && !$phpDocType) {
  2016. throw new Exception("Missing type for property $class::\$$property->name");
  2017. }
  2018. if ($property->default instanceof Expr\ConstFetch &&
  2019. $property->default->name->toLowerString() === "null" &&
  2020. $propertyType && !$propertyType->isNullable()
  2021. ) {
  2022. $simpleType = $propertyType->tryToSimpleType();
  2023. if ($simpleType === null) {
  2024. throw new Exception(
  2025. "Property $class::\$$property->name has null default, but is not nullable");
  2026. }
  2027. }
  2028. return new PropertyInfo(
  2029. new PropertyName($class, $property->name->__toString()),
  2030. $flags,
  2031. $propertyType,
  2032. $phpDocType ? Type::fromString($phpDocType) : null,
  2033. $property->default,
  2034. $property->default ? $prettyPrinter->prettyPrintExpr($property->default) : null,
  2035. $isDocReadonly,
  2036. $link
  2037. );
  2038. }
  2039. /**
  2040. * @param PropertyInfo[] $properties
  2041. * @param FuncInfo[] $methods
  2042. * @param EnumCaseInfo[] $enumCases
  2043. */
  2044. function parseClass(
  2045. Name $name, Stmt\ClassLike $class, array $properties, array $methods, array $enumCases
  2046. ): ClassInfo {
  2047. $flags = $class instanceof Class_ ? $class->flags : 0;
  2048. $comment = $class->getDocComment();
  2049. $alias = null;
  2050. $isDeprecated = false;
  2051. $isStrictProperties = false;
  2052. $isNotSerializable = false;
  2053. if ($comment) {
  2054. $tags = parseDocComment($comment);
  2055. foreach ($tags as $tag) {
  2056. if ($tag->name === 'alias') {
  2057. $alias = $tag->getValue();
  2058. } else if ($tag->name === 'deprecated') {
  2059. $isDeprecated = true;
  2060. } else if ($tag->name === 'strict-properties') {
  2061. $isStrictProperties = true;
  2062. } else if ($tag->name === 'not-serializable') {
  2063. $isNotSerializable = true;
  2064. }
  2065. }
  2066. }
  2067. $extends = [];
  2068. $implements = [];
  2069. if ($class instanceof Class_) {
  2070. $classKind = "class";
  2071. if ($class->extends) {
  2072. $extends[] = $class->extends;
  2073. }
  2074. $implements = $class->implements;
  2075. } elseif ($class instanceof Interface_) {
  2076. $classKind = "interface";
  2077. $extends = $class->extends;
  2078. } else if ($class instanceof Trait_) {
  2079. $classKind = "trait";
  2080. } else if ($class instanceof Enum_) {
  2081. $classKind = "enum";
  2082. $implements = $class->implements;
  2083. } else {
  2084. throw new Exception("Unknown class kind " . get_class($class));
  2085. }
  2086. return new ClassInfo(
  2087. $name,
  2088. $flags,
  2089. $classKind,
  2090. $alias,
  2091. $class instanceof Enum_ && $class->scalarType !== null
  2092. ? SimpleType::fromNode($class->scalarType) : null,
  2093. $isDeprecated,
  2094. $isStrictProperties,
  2095. $isNotSerializable,
  2096. $extends,
  2097. $implements,
  2098. $properties,
  2099. $methods,
  2100. $enumCases
  2101. );
  2102. }
  2103. function handlePreprocessorConditions(array &$conds, Stmt $stmt): ?string {
  2104. foreach ($stmt->getComments() as $comment) {
  2105. $text = trim($comment->getText());
  2106. if (preg_match('/^#\s*if\s+(.+)$/', $text, $matches)) {
  2107. $conds[] = $matches[1];
  2108. } else if (preg_match('/^#\s*ifdef\s+(.+)$/', $text, $matches)) {
  2109. $conds[] = "defined($matches[1])";
  2110. } else if (preg_match('/^#\s*ifndef\s+(.+)$/', $text, $matches)) {
  2111. $conds[] = "!defined($matches[1])";
  2112. } else if (preg_match('/^#\s*else$/', $text)) {
  2113. if (empty($conds)) {
  2114. throw new Exception("Encountered else without corresponding #if");
  2115. }
  2116. $cond = array_pop($conds);
  2117. $conds[] = "!($cond)";
  2118. } else if (preg_match('/^#\s*endif$/', $text)) {
  2119. if (empty($conds)) {
  2120. throw new Exception("Encountered #endif without corresponding #if");
  2121. }
  2122. array_pop($conds);
  2123. } else if ($text[0] === '#') {
  2124. throw new Exception("Unrecognized preprocessor directive \"$text\"");
  2125. }
  2126. }
  2127. return empty($conds) ? null : implode(' && ', $conds);
  2128. }
  2129. function getFileDocComment(array $stmts): ?DocComment {
  2130. if (empty($stmts)) {
  2131. return null;
  2132. }
  2133. $comments = $stmts[0]->getComments();
  2134. if (empty($comments)) {
  2135. return null;
  2136. }
  2137. if ($comments[0] instanceof DocComment) {
  2138. return $comments[0];
  2139. }
  2140. return null;
  2141. }
  2142. function handleStatements(FileInfo $fileInfo, array $stmts, PrettyPrinterAbstract $prettyPrinter) {
  2143. $conds = [];
  2144. foreach ($stmts as $stmt) {
  2145. if ($stmt instanceof Stmt\Nop) {
  2146. continue;
  2147. }
  2148. if ($stmt instanceof Stmt\Namespace_) {
  2149. handleStatements($fileInfo, $stmt->stmts, $prettyPrinter);
  2150. continue;
  2151. }
  2152. $cond = handlePreprocessorConditions($conds, $stmt);
  2153. if ($stmt instanceof Stmt\Function_) {
  2154. $fileInfo->funcInfos[] = parseFunctionLike(
  2155. $prettyPrinter,
  2156. new FunctionName($stmt->namespacedName),
  2157. 0,
  2158. 0,
  2159. $stmt,
  2160. $cond
  2161. );
  2162. continue;
  2163. }
  2164. if ($stmt instanceof Stmt\ClassLike) {
  2165. $className = $stmt->namespacedName;
  2166. $propertyInfos = [];
  2167. $methodInfos = [];
  2168. $enumCaseInfos = [];
  2169. foreach ($stmt->stmts as $classStmt) {
  2170. $cond = handlePreprocessorConditions($conds, $classStmt);
  2171. if ($classStmt instanceof Stmt\Nop) {
  2172. continue;
  2173. }
  2174. $classFlags = $stmt instanceof Class_ ? $stmt->flags : 0;
  2175. $abstractFlag = $stmt instanceof Stmt\Interface_ ? Class_::MODIFIER_ABSTRACT : 0;
  2176. if ($classStmt instanceof Stmt\Property) {
  2177. if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) {
  2178. throw new Exception("Visibility modifier is required");
  2179. }
  2180. foreach ($classStmt->props as $property) {
  2181. $propertyInfos[] = parseProperty(
  2182. $className,
  2183. $classStmt->flags,
  2184. $property,
  2185. $classStmt->type,
  2186. $classStmt->getDocComment(),
  2187. $prettyPrinter
  2188. );
  2189. }
  2190. } else if ($classStmt instanceof Stmt\ClassMethod) {
  2191. if (!($classStmt->flags & Class_::VISIBILITY_MODIFIER_MASK)) {
  2192. throw new Exception("Visibility modifier is required");
  2193. }
  2194. $methodInfos[] = parseFunctionLike(
  2195. $prettyPrinter,
  2196. new MethodName($className, $classStmt->name->toString()),
  2197. $classFlags,
  2198. $classStmt->flags | $abstractFlag,
  2199. $classStmt,
  2200. $cond
  2201. );
  2202. } else if ($classStmt instanceof Stmt\EnumCase) {
  2203. $enumCaseInfos[] = new EnumCaseInfo(
  2204. $classStmt->name->toString(), $classStmt->expr);
  2205. } else {
  2206. throw new Exception("Not implemented {$classStmt->getType()}");
  2207. }
  2208. }
  2209. $fileInfo->classInfos[] = parseClass(
  2210. $className, $stmt, $propertyInfos, $methodInfos, $enumCaseInfos);
  2211. continue;
  2212. }
  2213. throw new Exception("Unexpected node {$stmt->getType()}");
  2214. }
  2215. }
  2216. function parseStubFile(string $code): FileInfo {
  2217. $lexer = new PhpParser\Lexer\Emulative();
  2218. $parser = new PhpParser\Parser\Php7($lexer);
  2219. $nodeTraverser = new PhpParser\NodeTraverser;
  2220. $nodeTraverser->addVisitor(new PhpParser\NodeVisitor\NameResolver);
  2221. $prettyPrinter = new class extends Standard {
  2222. protected function pName_FullyQualified(Name\FullyQualified $node) {
  2223. return implode('\\', $node->parts);
  2224. }
  2225. };
  2226. $stmts = $parser->parse($code);
  2227. $nodeTraverser->traverse($stmts);
  2228. $fileInfo = new FileInfo;
  2229. $fileDocComment = getFileDocComment($stmts);
  2230. if ($fileDocComment) {
  2231. $fileTags = parseDocComment($fileDocComment);
  2232. foreach ($fileTags as $tag) {
  2233. if ($tag->name === 'generate-function-entries') {
  2234. $fileInfo->generateFunctionEntries = true;
  2235. $fileInfo->declarationPrefix = $tag->value ? $tag->value . " " : "";
  2236. } else if ($tag->name === 'generate-legacy-arginfo') {
  2237. $fileInfo->generateLegacyArginfo = true;
  2238. } else if ($tag->name === 'generate-class-entries') {
  2239. $fileInfo->generateClassEntries = true;
  2240. $fileInfo->declarationPrefix = $tag->value ? $tag->value . " " : "";
  2241. }
  2242. }
  2243. }
  2244. // Generating class entries require generating function/method entries
  2245. if ($fileInfo->generateClassEntries && !$fileInfo->generateFunctionEntries) {
  2246. $fileInfo->generateFunctionEntries = true;
  2247. }
  2248. handleStatements($fileInfo, $stmts, $prettyPrinter);
  2249. return $fileInfo;
  2250. }
  2251. function funcInfoToCode(FuncInfo $funcInfo): string {
  2252. $code = '';
  2253. $returnType = $funcInfo->return->type;
  2254. $isTentativeReturnType = $funcInfo->return->tentativeReturnType;
  2255. if ($returnType !== null) {
  2256. if (null !== $simpleReturnType = $returnType->tryToSimpleType()) {
  2257. if ($simpleReturnType->isBuiltin) {
  2258. $code .= sprintf(
  2259. "%s(%s, %d, %d, %s, %d)\n",
  2260. $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX",
  2261. $funcInfo->getArgInfoName(), $funcInfo->return->byRef,
  2262. $funcInfo->numRequiredArgs,
  2263. $simpleReturnType->toTypeCode(), $returnType->isNullable()
  2264. );
  2265. } else {
  2266. $code .= sprintf(
  2267. "%s(%s, %d, %d, %s, %d)\n",
  2268. $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_INFO_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX",
  2269. $funcInfo->getArgInfoName(), $funcInfo->return->byRef,
  2270. $funcInfo->numRequiredArgs,
  2271. $simpleReturnType->toEscapedName(), $returnType->isNullable()
  2272. );
  2273. }
  2274. } else {
  2275. $arginfoType = $returnType->toArginfoType();
  2276. if ($arginfoType->hasClassType()) {
  2277. $code .= sprintf(
  2278. "%s(%s, %d, %d, %s, %s)\n",
  2279. $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_OBJ_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX",
  2280. $funcInfo->getArgInfoName(), $funcInfo->return->byRef,
  2281. $funcInfo->numRequiredArgs,
  2282. $arginfoType->toClassTypeString(), $arginfoType->toTypeMask()
  2283. );
  2284. } else {
  2285. $code .= sprintf(
  2286. "%s(%s, %d, %d, %s)\n",
  2287. $isTentativeReturnType ? "ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_MASK_EX" : "ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX",
  2288. $funcInfo->getArgInfoName(), $funcInfo->return->byRef,
  2289. $funcInfo->numRequiredArgs,
  2290. $arginfoType->toTypeMask()
  2291. );
  2292. }
  2293. }
  2294. } else {
  2295. $code .= sprintf(
  2296. "ZEND_BEGIN_ARG_INFO_EX(%s, 0, %d, %d)\n",
  2297. $funcInfo->getArgInfoName(), $funcInfo->return->byRef, $funcInfo->numRequiredArgs
  2298. );
  2299. }
  2300. foreach ($funcInfo->args as $argInfo) {
  2301. $argKind = $argInfo->isVariadic ? "ARG_VARIADIC" : "ARG";
  2302. $argDefaultKind = $argInfo->hasProperDefaultValue() ? "_WITH_DEFAULT_VALUE" : "";
  2303. $argType = $argInfo->type;
  2304. if ($argType !== null) {
  2305. if (null !== $simpleArgType = $argType->tryToSimpleType()) {
  2306. if ($simpleArgType->isBuiltin) {
  2307. $code .= sprintf(
  2308. "\tZEND_%s_TYPE_INFO%s(%s, %s, %s, %d%s)\n",
  2309. $argKind, $argDefaultKind, $argInfo->getSendByString(), $argInfo->name,
  2310. $simpleArgType->toTypeCode(), $argType->isNullable(),
  2311. $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
  2312. );
  2313. } else {
  2314. $code .= sprintf(
  2315. "\tZEND_%s_OBJ_INFO%s(%s, %s, %s, %d%s)\n",
  2316. $argKind,$argDefaultKind, $argInfo->getSendByString(), $argInfo->name,
  2317. $simpleArgType->toEscapedName(), $argType->isNullable(),
  2318. $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
  2319. );
  2320. }
  2321. } else {
  2322. $arginfoType = $argType->toArginfoType();
  2323. if ($arginfoType->hasClassType()) {
  2324. $code .= sprintf(
  2325. "\tZEND_%s_OBJ_TYPE_MASK(%s, %s, %s, %s, %s)\n",
  2326. $argKind, $argInfo->getSendByString(), $argInfo->name,
  2327. $arginfoType->toClassTypeString(), $arginfoType->toTypeMask(),
  2328. $argInfo->getDefaultValueAsArginfoString()
  2329. );
  2330. } else {
  2331. $code .= sprintf(
  2332. "\tZEND_%s_TYPE_MASK(%s, %s, %s, %s)\n",
  2333. $argKind, $argInfo->getSendByString(), $argInfo->name,
  2334. $arginfoType->toTypeMask(),
  2335. $argInfo->getDefaultValueAsArginfoString()
  2336. );
  2337. }
  2338. }
  2339. } else {
  2340. $code .= sprintf(
  2341. "\tZEND_%s_INFO%s(%s, %s%s)\n",
  2342. $argKind, $argDefaultKind, $argInfo->getSendByString(), $argInfo->name,
  2343. $argInfo->hasProperDefaultValue() ? ", " . $argInfo->getDefaultValueAsArginfoString() : ""
  2344. );
  2345. }
  2346. }
  2347. $code .= "ZEND_END_ARG_INFO()";
  2348. return $code . "\n";
  2349. }
  2350. /** @param FuncInfo[] $generatedFuncInfos */
  2351. function findEquivalentFuncInfo(array $generatedFuncInfos, FuncInfo $funcInfo): ?FuncInfo {
  2352. foreach ($generatedFuncInfos as $generatedFuncInfo) {
  2353. if ($generatedFuncInfo->equalsApartFromNameAndRefcount($funcInfo)) {
  2354. return $generatedFuncInfo;
  2355. }
  2356. }
  2357. return null;
  2358. }
  2359. /** @param iterable<FuncInfo> $funcInfos */
  2360. function generateCodeWithConditions(
  2361. iterable $funcInfos, string $separator, Closure $codeGenerator): string {
  2362. $code = "";
  2363. foreach ($funcInfos as $funcInfo) {
  2364. $funcCode = $codeGenerator($funcInfo);
  2365. if ($funcCode === null) {
  2366. continue;
  2367. }
  2368. $code .= $separator;
  2369. if ($funcInfo->cond) {
  2370. $code .= "#if {$funcInfo->cond}\n";
  2371. $code .= $funcCode;
  2372. $code .= "#endif\n";
  2373. } else {
  2374. $code .= $funcCode;
  2375. }
  2376. }
  2377. return $code;
  2378. }
  2379. function generateArgInfoCode(FileInfo $fileInfo, string $stubHash): string {
  2380. $code = "/* This is a generated file, edit the .stub.php file instead.\n"
  2381. . " * Stub hash: $stubHash */\n";
  2382. $generatedFuncInfos = [];
  2383. $code .= generateCodeWithConditions(
  2384. $fileInfo->getAllFuncInfos(), "\n",
  2385. function (FuncInfo $funcInfo) use(&$generatedFuncInfos) {
  2386. /* If there already is an equivalent arginfo structure, only emit a #define */
  2387. if ($generatedFuncInfo = findEquivalentFuncInfo($generatedFuncInfos, $funcInfo)) {
  2388. $code = sprintf(
  2389. "#define %s %s\n",
  2390. $funcInfo->getArgInfoName(), $generatedFuncInfo->getArgInfoName()
  2391. );
  2392. } else {
  2393. $code = funcInfoToCode($funcInfo);
  2394. }
  2395. $generatedFuncInfos[] = $funcInfo;
  2396. return $code;
  2397. }
  2398. );
  2399. if ($fileInfo->generateFunctionEntries) {
  2400. $code .= "\n\n";
  2401. $generatedFunctionDeclarations = [];
  2402. $code .= generateCodeWithConditions(
  2403. $fileInfo->getAllFuncInfos(), "",
  2404. function (FuncInfo $funcInfo) use($fileInfo, &$generatedFunctionDeclarations) {
  2405. $key = $funcInfo->getDeclarationKey();
  2406. if (isset($generatedFunctionDeclarations[$key])) {
  2407. return null;
  2408. }
  2409. $generatedFunctionDeclarations[$key] = true;
  2410. return $fileInfo->declarationPrefix . $funcInfo->getDeclaration();
  2411. }
  2412. );
  2413. if (!empty($fileInfo->funcInfos)) {
  2414. $code .= generateFunctionEntries(null, $fileInfo->funcInfos);
  2415. }
  2416. foreach ($fileInfo->classInfos as $classInfo) {
  2417. $code .= generateFunctionEntries($classInfo->name, $classInfo->funcInfos);
  2418. }
  2419. }
  2420. if ($fileInfo->generateClassEntries) {
  2421. $code .= generateClassEntryCode($fileInfo);
  2422. }
  2423. return $code;
  2424. }
  2425. function generateClassEntryCode(FileInfo $fileInfo): string {
  2426. $code = "";
  2427. foreach ($fileInfo->classInfos as $class) {
  2428. $code .= "\n" . $class->getRegistration();
  2429. }
  2430. return $code;
  2431. }
  2432. /** @param FuncInfo[] $funcInfos */
  2433. function generateFunctionEntries(?Name $className, array $funcInfos): string {
  2434. $code = "";
  2435. $functionEntryName = "ext_functions";
  2436. if ($className) {
  2437. $underscoreName = implode("_", $className->parts);
  2438. $functionEntryName = "class_{$underscoreName}_methods";
  2439. }
  2440. $code .= "\n\nstatic const zend_function_entry {$functionEntryName}[] = {\n";
  2441. $code .= generateCodeWithConditions($funcInfos, "", function (FuncInfo $funcInfo) {
  2442. return $funcInfo->getFunctionEntry();
  2443. });
  2444. $code .= "\tZEND_FE_END\n";
  2445. $code .= "};\n";
  2446. return $code;
  2447. }
  2448. /** @param FuncInfo<string, FuncInfo> $funcInfos */
  2449. function generateOptimizerInfo(array $funcInfos): string {
  2450. $code = "/* This is a generated file, edit the .stub.php files instead. */\n\n";
  2451. $code .= "static const func_info_t func_infos[] = {\n";
  2452. $code .= generateCodeWithConditions($funcInfos, "", function (FuncInfo $funcInfo) {
  2453. return $funcInfo->getOptimizerInfo();
  2454. });
  2455. $code .= "};\n";
  2456. return $code;
  2457. }
  2458. /**
  2459. * @param ClassInfo[] $classMap
  2460. * @return array<string, string>
  2461. */
  2462. function generateClassSynopses(array $classMap): array {
  2463. $result = [];
  2464. foreach ($classMap as $classInfo) {
  2465. $classSynopsis = $classInfo->getClassSynopsisDocument($classMap);
  2466. if ($classSynopsis !== null) {
  2467. $result[ClassInfo::getClassSynopsisFilename($classInfo->name) . ".xml"] = $classSynopsis;
  2468. }
  2469. }
  2470. return $result;
  2471. }
  2472. /**
  2473. * @param ClassInfo[] $classMap
  2474. * @return array<string, string>
  2475. */
  2476. function replaceClassSynopses(string $targetDirectory, array $classMap): array
  2477. {
  2478. $classSynopses = [];
  2479. $it = new RecursiveIteratorIterator(
  2480. new RecursiveDirectoryIterator($targetDirectory),
  2481. RecursiveIteratorIterator::LEAVES_ONLY
  2482. );
  2483. foreach ($it as $file) {
  2484. $pathName = $file->getPathName();
  2485. if (!preg_match('/\.xml$/i', $pathName)) {
  2486. continue;
  2487. }
  2488. $xml = file_get_contents($pathName);
  2489. if ($xml === false) {
  2490. continue;
  2491. }
  2492. if (stripos($xml, "<classsynopsis") === false) {
  2493. continue;
  2494. }
  2495. $replacedXml = getReplacedSynopsisXml($xml);
  2496. $doc = new DOMDocument();
  2497. $doc->formatOutput = false;
  2498. $doc->preserveWhiteSpace = true;
  2499. $doc->validateOnParse = true;
  2500. $success = $doc->loadXML($replacedXml);
  2501. if (!$success) {
  2502. echo "Failed opening $pathName\n";
  2503. continue;
  2504. }
  2505. $classSynopsisElements = [];
  2506. foreach ($doc->getElementsByTagName("classsynopsis") as $element) {
  2507. $classSynopsisElements[] = $element;
  2508. }
  2509. foreach ($classSynopsisElements as $classSynopsis) {
  2510. if (!$classSynopsis instanceof DOMElement) {
  2511. continue;
  2512. }
  2513. $firstChild = $classSynopsis->firstElementChild;
  2514. if ($firstChild === null) {
  2515. continue;
  2516. }
  2517. $firstChild = $firstChild->firstElementChild;
  2518. if ($firstChild === null) {
  2519. continue;
  2520. }
  2521. $className = $firstChild->textContent;
  2522. if (!isset($classMap[$className])) {
  2523. continue;
  2524. }
  2525. $classInfo = $classMap[$className];
  2526. $newClassSynopsis = $classInfo->getClassSynopsisElement($doc, $classMap);
  2527. if ($newClassSynopsis === null) {
  2528. continue;
  2529. }
  2530. // Check if there is any change - short circuit if there is not any.
  2531. if (replaceAndCompareXmls($doc, $classSynopsis, $newClassSynopsis)) {
  2532. continue;
  2533. }
  2534. // Return the updated XML
  2535. $replacedXml = $doc->saveXML();
  2536. $replacedXml = preg_replace(
  2537. [
  2538. "/REPLACED-ENTITY-([A-Za-z0-9._{}%-]+?;)/",
  2539. "/<phpdoc:(classref|exceptionref)\s+xmlns:phpdoc=\"([a-z0-9.:\/]+)\"\s+xmlns=\"([a-z0-9.:\/]+)\"\s+xml:id=\"([a-z0-9._-]+)\"\s*>/i",
  2540. "/<phpdoc:(classref|exceptionref)\s+xmlns:phpdoc=\"([a-z0-9.:\/]+)\"\s+xmlns=\"([a-z0-9.:\/]+)\"\s+xmlns:xi=\"([a-z0-9.:\/]+)\"\s+xml:id=\"([a-z0-9._-]+)\"\s*>/i",
  2541. "/<phpdoc:(classref|exceptionref)\s+xmlns:phpdoc=\"([a-z0-9.:\/]+)\"\s+xmlns=\"([a-z0-9.:\/]+)\"\s+xmlns:xlink=\"([a-z0-9.:\/]+)\"\s+xmlns:xi=\"([a-z0-9.:\/]+)\"\s+xml:id=\"([a-z0-9._-]+)\"\s*>/i",
  2542. "/<phpdoc:(classref|exceptionref)\s+xmlns=\"([a-z0-9.:\/]+)\"\s+xmlns:xlink=\"([a-z0-9.:\/]+)\"\s+xmlns:xi=\"([a-z0-9.:\/]+)\"\s+xmlns:phpdoc=\"([a-z0-9.:\/]+)\"\s+xml:id=\"([a-z0-9._-]+)\"\s*>/i",
  2543. ],
  2544. [
  2545. "&$1",
  2546. "<phpdoc:$1 xml:id=\"$4\" xmlns:phpdoc=\"$2\" xmlns=\"$3\">",
  2547. "<phpdoc:$1 xml:id=\"$5\" xmlns:phpdoc=\"$2\" xmlns=\"$3\" xmlns:xi=\"$4\">",
  2548. "<phpdoc:$1 xml:id=\"$6\" xmlns:phpdoc=\"$2\" xmlns=\"$3\" xmlns:xlink=\"$4\" xmlns:xi=\"$5\">",
  2549. "<phpdoc:$1 xml:id=\"$6\" xmlns:phpdoc=\"$5\" xmlns=\"$2\" xmlns:xlink=\"$3\" xmlns:xi=\"$4\">",
  2550. ],
  2551. $replacedXml
  2552. );
  2553. $classSynopses[$pathName] = $replacedXml;
  2554. }
  2555. }
  2556. return $classSynopses;
  2557. }
  2558. function getReplacedSynopsisXml(string $xml): string
  2559. {
  2560. return preg_replace(
  2561. [
  2562. "/&([A-Za-z0-9._{}%-]+?;)/",
  2563. "/<(\/)*xi:([A-Za-z]+?)/"
  2564. ],
  2565. [
  2566. "REPLACED-ENTITY-$1",
  2567. "<$1XI$2",
  2568. ],
  2569. $xml
  2570. );
  2571. }
  2572. /**
  2573. * @param array<string, FuncInfo> $funcMap
  2574. * @param array<string, FuncInfo> $aliasMap
  2575. * @return array<string, string>
  2576. */
  2577. function generateMethodSynopses(array $funcMap, array $aliasMap): array {
  2578. $result = [];
  2579. foreach ($funcMap as $funcInfo) {
  2580. $methodSynopsis = $funcInfo->getMethodSynopsisDocument($funcMap, $aliasMap);
  2581. if ($methodSynopsis !== null) {
  2582. $result[$funcInfo->name->getMethodSynopsisFilename() . ".xml"] = $methodSynopsis;
  2583. }
  2584. }
  2585. return $result;
  2586. }
  2587. /**
  2588. * @param array<string, FuncInfo> $funcMap
  2589. * @param array<string, FuncInfo> $aliasMap
  2590. * @return array<string, string>
  2591. */
  2592. function replaceMethodSynopses(string $targetDirectory, array $funcMap, array $aliasMap): array {
  2593. $methodSynopses = [];
  2594. $it = new RecursiveIteratorIterator(
  2595. new RecursiveDirectoryIterator($targetDirectory),
  2596. RecursiveIteratorIterator::LEAVES_ONLY
  2597. );
  2598. foreach ($it as $file) {
  2599. $pathName = $file->getPathName();
  2600. if (!preg_match('/\.xml$/i', $pathName)) {
  2601. continue;
  2602. }
  2603. $xml = file_get_contents($pathName);
  2604. if ($xml === false) {
  2605. continue;
  2606. }
  2607. if (stripos($xml, "<methodsynopsis") === false && stripos($xml, "<constructorsynopsis") === false && stripos($xml, "<destructorsynopsis") === false) {
  2608. continue;
  2609. }
  2610. $replacedXml = getReplacedSynopsisXml($xml);
  2611. $doc = new DOMDocument();
  2612. $doc->formatOutput = false;
  2613. $doc->preserveWhiteSpace = true;
  2614. $doc->validateOnParse = true;
  2615. $success = $doc->loadXML($replacedXml);
  2616. if (!$success) {
  2617. echo "Failed opening $pathName\n";
  2618. continue;
  2619. }
  2620. $methodSynopsisElements = [];
  2621. foreach ($doc->getElementsByTagName("constructorsynopsis") as $element) {
  2622. $methodSynopsisElements[] = $element;
  2623. }
  2624. foreach ($doc->getElementsByTagName("destructorsynopsis") as $element) {
  2625. $methodSynopsisElements[] = $element;
  2626. }
  2627. foreach ($doc->getElementsByTagName("methodsynopsis") as $element) {
  2628. $methodSynopsisElements[] = $element;
  2629. }
  2630. foreach ($methodSynopsisElements as $methodSynopsis) {
  2631. if (!$methodSynopsis instanceof DOMElement) {
  2632. continue;
  2633. }
  2634. $list = $methodSynopsis->getElementsByTagName("methodname");
  2635. $item = $list->item(0);
  2636. if (!$item instanceof DOMElement) {
  2637. continue;
  2638. }
  2639. $funcName = $item->textContent;
  2640. if (!isset($funcMap[$funcName])) {
  2641. continue;
  2642. }
  2643. $funcInfo = $funcMap[$funcName];
  2644. $newMethodSynopsis = $funcInfo->getMethodSynopsisElement($funcMap, $aliasMap, $doc);
  2645. if ($newMethodSynopsis === null) {
  2646. continue;
  2647. }
  2648. // Retrieve current signature
  2649. $params = [];
  2650. $list = $methodSynopsis->getElementsByTagName("methodparam");
  2651. foreach ($list as $i => $item) {
  2652. if (!$item instanceof DOMElement) {
  2653. continue;
  2654. }
  2655. $paramList = $item->getElementsByTagName("parameter");
  2656. if ($paramList->count() !== 1) {
  2657. continue;
  2658. }
  2659. $paramName = $paramList->item(0)->textContent;
  2660. $paramTypes = [];
  2661. $paramList = $item->getElementsByTagName("type");
  2662. foreach ($paramList as $type) {
  2663. if (!$type instanceof DOMElement) {
  2664. continue;
  2665. }
  2666. $paramTypes[] = $type->textContent;
  2667. }
  2668. $params[$paramName] = ["index" => $i, "type" => $paramTypes];
  2669. }
  2670. // Check if there is any change - short circuit if there is not any.
  2671. if (replaceAndCompareXmls($doc, $methodSynopsis, $newMethodSynopsis)) {
  2672. continue;
  2673. }
  2674. // Update parameter references
  2675. $paramList = $doc->getElementsByTagName("parameter");
  2676. /** @var DOMElement $paramElement */
  2677. foreach ($paramList as $paramElement) {
  2678. if ($paramElement->parentNode && $paramElement->parentNode->nodeName === "methodparam") {
  2679. continue;
  2680. }
  2681. $name = $paramElement->textContent;
  2682. if (!isset($params[$name])) {
  2683. continue;
  2684. }
  2685. $index = $params[$name]["index"];
  2686. if (!isset($funcInfo->args[$index])) {
  2687. continue;
  2688. }
  2689. $paramElement->textContent = $funcInfo->args[$index]->name;
  2690. }
  2691. // Return the updated XML
  2692. $replacedXml = $doc->saveXML();
  2693. $replacedXml = preg_replace(
  2694. [
  2695. "/REPLACED-ENTITY-([A-Za-z0-9._{}%-]+?;)/",
  2696. "/<refentry\s+xmlns=\"([a-z0-9.:\/]+)\"\s+xml:id=\"([a-z0-9._-]+)\"\s*>/i",
  2697. "/<refentry\s+xmlns=\"([a-z0-9.:\/]+)\"\s+xmlns:xlink=\"([a-z0-9.:\/]+)\"\s+xml:id=\"([a-z0-9._-]+)\"\s*>/i",
  2698. ],
  2699. [
  2700. "&$1",
  2701. "<refentry xml:id=\"$2\" xmlns=\"$1\">",
  2702. "<refentry xml:id=\"$3\" xmlns=\"$1\" xmlns:xlink=\"$2\">",
  2703. ],
  2704. $replacedXml
  2705. );
  2706. $methodSynopses[$pathName] = $replacedXml;
  2707. }
  2708. }
  2709. return $methodSynopses;
  2710. }
  2711. function replaceAndCompareXmls(DOMDocument $doc, DOMElement $originalSynopsis, DOMElement $newSynopsis): bool
  2712. {
  2713. $docComparator = new DOMDocument();
  2714. $docComparator->preserveWhiteSpace = false;
  2715. $docComparator->formatOutput = true;
  2716. $xml1 = $doc->saveXML($originalSynopsis);
  2717. $xml1 = getReplacedSynopsisXml($xml1);
  2718. $docComparator->loadXML($xml1);
  2719. $xml1 = $docComparator->saveXML();
  2720. $originalSynopsis->parentNode->replaceChild($newSynopsis, $originalSynopsis);
  2721. $xml2 = $doc->saveXML($newSynopsis);
  2722. $xml2 = getReplacedSynopsisXml($xml2);
  2723. $docComparator->loadXML($xml2);
  2724. $xml2 = $docComparator->saveXML();
  2725. return $xml1 === $xml2;
  2726. }
  2727. function installPhpParser(string $version, string $phpParserDir) {
  2728. $lockFile = __DIR__ . "/PHP-Parser-install-lock";
  2729. $lockFd = fopen($lockFile, 'w+');
  2730. if (!flock($lockFd, LOCK_EX)) {
  2731. throw new Exception("Failed to acquire installation lock");
  2732. }
  2733. try {
  2734. // Check whether a parallel process has already installed PHP-Parser.
  2735. if (is_dir($phpParserDir)) {
  2736. return;
  2737. }
  2738. $cwd = getcwd();
  2739. chdir(__DIR__);
  2740. $tarName = "v$version.tar.gz";
  2741. passthru("wget https://github.com/nikic/PHP-Parser/archive/$tarName", $exit);
  2742. if ($exit !== 0) {
  2743. passthru("curl -LO https://github.com/nikic/PHP-Parser/archive/$tarName", $exit);
  2744. }
  2745. if ($exit !== 0) {
  2746. throw new Exception("Failed to download PHP-Parser tarball");
  2747. }
  2748. if (!mkdir($phpParserDir)) {
  2749. throw new Exception("Failed to create directory $phpParserDir");
  2750. }
  2751. passthru("tar xvzf $tarName -C PHP-Parser-$version --strip-components 1", $exit);
  2752. if ($exit !== 0) {
  2753. throw new Exception("Failed to extract PHP-Parser tarball");
  2754. }
  2755. unlink(__DIR__ . "/$tarName");
  2756. chdir($cwd);
  2757. } finally {
  2758. flock($lockFd, LOCK_UN);
  2759. @unlink($lockFile);
  2760. }
  2761. }
  2762. function initPhpParser() {
  2763. static $isInitialized = false;
  2764. if ($isInitialized) {
  2765. return;
  2766. }
  2767. if (!extension_loaded("tokenizer")) {
  2768. throw new Exception("The \"tokenizer\" extension is not available");
  2769. }
  2770. $isInitialized = true;
  2771. $version = "4.13.0";
  2772. $phpParserDir = __DIR__ . "/PHP-Parser-$version";
  2773. if (!is_dir($phpParserDir)) {
  2774. installPhpParser($version, $phpParserDir);
  2775. }
  2776. spl_autoload_register(function(string $class) use($phpParserDir) {
  2777. if (strpos($class, "PhpParser\\") === 0) {
  2778. $fileName = $phpParserDir . "/lib/" . str_replace("\\", "/", $class) . ".php";
  2779. require $fileName;
  2780. }
  2781. });
  2782. }
  2783. $optind = null;
  2784. $options = getopt(
  2785. "fh",
  2786. [
  2787. "force-regeneration", "parameter-stats", "help", "verify", "generate-classsynopses", "replace-classsynopses",
  2788. "generate-methodsynopses", "replace-methodsynopses", "generate-optimizer-info"
  2789. ],
  2790. $optind
  2791. );
  2792. $context = new Context;
  2793. $printParameterStats = isset($options["parameter-stats"]);
  2794. $verify = isset($options["verify"]);
  2795. $generateClassSynopses = isset($options["generate-classsynopses"]);
  2796. $replaceClassSynopses = isset($options["replace-classsynopses"]);
  2797. $generateMethodSynopses = isset($options["generate-methodsynopses"]);
  2798. $replaceMethodSynopses = isset($options["replace-methodsynopses"]);
  2799. $generateOptimizerInfo = isset($options["generate-optimizer-info"]);
  2800. $context->forceRegeneration = isset($options["f"]) || isset($options["force-regeneration"]);
  2801. $context->forceParse = $context->forceRegeneration || $printParameterStats || $verify || $generateClassSynopses || $generateOptimizerInfo || $replaceClassSynopses || $generateMethodSynopses || $replaceMethodSynopses;
  2802. $targetSynopses = $argv[$argc - 1] ?? null;
  2803. if ($replaceClassSynopses && $targetSynopses === null) {
  2804. die("A target class synopsis directory must be provided for.\n");
  2805. }
  2806. if ($replaceMethodSynopses && $targetSynopses === null) {
  2807. die("A target method synopsis directory must be provided.\n");
  2808. }
  2809. if (isset($options["h"]) || isset($options["help"])) {
  2810. die("\nusage: gen_stub.php [ -f | --force-regeneration ] [ --generate-classsynopses ] [ --replace-classsynopses ] [ --generate-methodsynopses ] [ --replace-methodsynopses ] [ --parameter-stats ] [ --verify ] [ --generate-optimizer-info ] [ -h | --help ] [ name.stub.php | directory ] [ directory ]\n\n");
  2811. }
  2812. $fileInfos = [];
  2813. $locations = array_slice($argv, $optind) ?: ['.'];
  2814. foreach (array_unique($locations) as $location) {
  2815. if (is_file($location)) {
  2816. // Generate single file.
  2817. $fileInfo = processStubFile($location, $context);
  2818. if ($fileInfo) {
  2819. $fileInfos[] = $fileInfo;
  2820. }
  2821. } else if (is_dir($location)) {
  2822. array_push($fileInfos, ...processDirectory($location, $context));
  2823. } else {
  2824. echo "$location is neither a file nor a directory.\n";
  2825. exit(1);
  2826. }
  2827. }
  2828. if ($printParameterStats) {
  2829. $parameterStats = [];
  2830. foreach ($fileInfos as $fileInfo) {
  2831. foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
  2832. foreach ($funcInfo->args as $argInfo) {
  2833. if (!isset($parameterStats[$argInfo->name])) {
  2834. $parameterStats[$argInfo->name] = 0;
  2835. }
  2836. $parameterStats[$argInfo->name]++;
  2837. }
  2838. }
  2839. }
  2840. arsort($parameterStats);
  2841. echo json_encode($parameterStats, JSON_PRETTY_PRINT), "\n";
  2842. }
  2843. /** @var array<string, ClassInfo> $classMap */
  2844. $classMap = [];
  2845. /** @var array<string, FuncInfo> $funcMap */
  2846. $funcMap = [];
  2847. /** @var array<string, FuncInfo> $aliasMap */
  2848. $aliasMap = [];
  2849. foreach ($fileInfos as $fileInfo) {
  2850. foreach ($fileInfo->getAllFuncInfos() as $funcInfo) {
  2851. /** @var FuncInfo $funcInfo */
  2852. $funcMap[$funcInfo->name->__toString()] = $funcInfo;
  2853. // TODO: Don't use aliasMap for methodsynopsis?
  2854. if ($funcInfo->aliasType === "alias") {
  2855. $aliasMap[$funcInfo->alias->__toString()] = $funcInfo;
  2856. }
  2857. }
  2858. foreach ($fileInfo->classInfos as $classInfo) {
  2859. $classMap[$classInfo->name->__toString()] = $classInfo;
  2860. }
  2861. }
  2862. if ($verify) {
  2863. $errors = [];
  2864. foreach ($funcMap as $aliasFunc) {
  2865. if (!$aliasFunc->alias) {
  2866. continue;
  2867. }
  2868. if (!isset($funcMap[$aliasFunc->alias->__toString()])) {
  2869. $errors[] = "Aliased function {$aliasFunc->alias}() cannot be found";
  2870. continue;
  2871. }
  2872. if (!$aliasFunc->verify) {
  2873. continue;
  2874. }
  2875. $aliasedFunc = $funcMap[$aliasFunc->alias->__toString()];
  2876. $aliasedArgs = $aliasedFunc->args;
  2877. $aliasArgs = $aliasFunc->args;
  2878. if ($aliasFunc->isInstanceMethod() !== $aliasedFunc->isInstanceMethod()) {
  2879. if ($aliasFunc->isInstanceMethod()) {
  2880. $aliasedArgs = array_slice($aliasedArgs, 1);
  2881. }
  2882. if ($aliasedFunc->isInstanceMethod()) {
  2883. $aliasArgs = array_slice($aliasArgs, 1);
  2884. }
  2885. }
  2886. array_map(
  2887. function(?ArgInfo $aliasArg, ?ArgInfo $aliasedArg) use ($aliasFunc, $aliasedFunc, &$errors) {
  2888. if ($aliasArg === null) {
  2889. assert($aliasedArg !== null);
  2890. $errors[] = "{$aliasFunc->name}(): Argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() is missing";
  2891. return null;
  2892. }
  2893. if ($aliasedArg === null) {
  2894. $errors[] = "{$aliasedFunc->name}(): Argument \$$aliasArg->name of alias function {$aliasFunc->name}() is missing";
  2895. return null;
  2896. }
  2897. if ($aliasArg->name !== $aliasedArg->name) {
  2898. $errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same name";
  2899. return null;
  2900. }
  2901. if ($aliasArg->type != $aliasedArg->type) {
  2902. $errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same type";
  2903. }
  2904. if ($aliasArg->defaultValue !== $aliasedArg->defaultValue) {
  2905. $errors[] = "{$aliasFunc->name}(): Argument \$$aliasArg->name and argument \$$aliasedArg->name of aliased function {$aliasedFunc->name}() must have the same default value";
  2906. }
  2907. },
  2908. $aliasArgs, $aliasedArgs
  2909. );
  2910. $aliasedReturn = $aliasedFunc->return;
  2911. $aliasReturn = $aliasFunc->return;
  2912. if (!$aliasedFunc->name->isConstructor() && !$aliasFunc->name->isConstructor()) {
  2913. $aliasedReturnType = $aliasedReturn->type ?? $aliasedReturn->phpDocType;
  2914. $aliasReturnType = $aliasReturn->type ?? $aliasReturn->phpDocType;
  2915. if ($aliasReturnType != $aliasedReturnType) {
  2916. $errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same return type";
  2917. }
  2918. }
  2919. $aliasedPhpDocReturnType = $aliasedReturn->phpDocType;
  2920. $aliasPhpDocReturnType = $aliasReturn->phpDocType;
  2921. if ($aliasedPhpDocReturnType != $aliasPhpDocReturnType && $aliasedPhpDocReturnType != $aliasReturn->type && $aliasPhpDocReturnType != $aliasedReturn->type) {
  2922. $errors[] = "{$aliasFunc->name}() and {$aliasedFunc->name}() must have the same PHPDoc return type";
  2923. }
  2924. }
  2925. echo implode("\n", $errors);
  2926. if (!empty($errors)) {
  2927. echo "\n";
  2928. exit(1);
  2929. }
  2930. }
  2931. if ($generateClassSynopses) {
  2932. $classSynopsesDirectory = getcwd() . "/classsynopses";
  2933. $classSynopses = generateClassSynopses($classMap);
  2934. if (!empty($classSynopses)) {
  2935. if (!file_exists($classSynopsesDirectory)) {
  2936. mkdir($classSynopsesDirectory);
  2937. }
  2938. foreach ($classSynopses as $filename => $content) {
  2939. if (file_put_contents("$classSynopsesDirectory/$filename", $content)) {
  2940. echo "Saved $filename\n";
  2941. }
  2942. }
  2943. }
  2944. }
  2945. if ($replaceClassSynopses) {
  2946. $classSynopses = replaceClassSynopses($targetSynopses, $classMap);
  2947. foreach ($classSynopses as $filename => $content) {
  2948. if (file_put_contents($filename, $content)) {
  2949. echo "Saved $filename\n";
  2950. }
  2951. }
  2952. }
  2953. if ($generateMethodSynopses) {
  2954. $methodSynopsesDirectory = getcwd() . "/methodsynopses";
  2955. $methodSynopses = generateMethodSynopses($funcMap, $aliasMap);
  2956. if (!empty($methodSynopses)) {
  2957. if (!file_exists($methodSynopsesDirectory)) {
  2958. mkdir($methodSynopsesDirectory);
  2959. }
  2960. foreach ($methodSynopses as $filename => $content) {
  2961. if (file_put_contents("$methodSynopsesDirectory/$filename", $content)) {
  2962. echo "Saved $filename\n";
  2963. }
  2964. }
  2965. }
  2966. }
  2967. if ($replaceMethodSynopses) {
  2968. $methodSynopses = replaceMethodSynopses($targetSynopses, $funcMap, $aliasMap);
  2969. foreach ($methodSynopses as $filename => $content) {
  2970. if (file_put_contents($filename, $content)) {
  2971. echo "Saved $filename\n";
  2972. }
  2973. }
  2974. }
  2975. if ($generateOptimizerInfo) {
  2976. $filename = dirname(__FILE__, 2) . "/Zend/Optimizer/zend_func_infos.h";
  2977. $optimizerInfo = generateOptimizerInfo($funcMap);
  2978. if (file_put_contents($filename, $optimizerInfo)) {
  2979. echo "Saved $filename\n";
  2980. }
  2981. }