2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
17 #include "hphp/runtime/vm/unit-parser.h"
20 #include <condition_variable>
29 #include <sys/types.h>
32 #include <folly/compression/Zstd.h>
33 #include <folly/DynamicConverter.h>
34 #include <folly/json.h>
35 #include <folly/FileUtil.h>
36 #include <folly/system/ThreadName.h>
38 #include "hphp/hack/src/hackc/ffi_bridge/compiler_ffi.rs"
39 #include "hphp/hack/src/hackc/hhbc-ast.h"
40 #include "hphp/hack/src/parser/ffi_bridge/parser_ffi.rs"
41 #include "hphp/runtime/base/autoload-map.h"
42 #include "hphp/runtime/base/autoload-handler.h"
43 #include "hphp/runtime/base/file-stream-wrapper.h"
44 #include "hphp/runtime/base/ini-setting.h"
45 #include "hphp/runtime/base/stream-wrapper-registry.h"
46 #include "hphp/runtime/base/unit-cache.h"
47 #include "hphp/runtime/vm/disas.h"
48 #include "hphp/runtime/vm/native.h"
49 #include "hphp/runtime/vm/decl-provider.h"
50 #include "hphp/runtime/vm/hackc-translator.h"
51 #include "hphp/runtime/vm/unit-emitter.h"
52 #include "hphp/runtime/vm/unit-gen-helpers.h"
53 #include "hphp/util/atomic-vector.h"
54 #include "hphp/util/embedded-data.h"
55 #include "hphp/util/gzip.h"
56 #include "hphp/util/hackc-log.h"
57 #include "hphp/util/light-process.h"
58 #include "hphp/util/logger.h"
59 #include "hphp/util/match.h"
60 #include "hphp/util/sha1.h"
61 #include "hphp/util/struct-log.h"
62 #include "hphp/util/timer.h"
63 #include "hphp/zend/zend-strtod.h"
67 TRACE_SET_MOD(unit_parse
);
69 UnitEmitterCacheHook g_unit_emitter_cache_hook
= nullptr;
70 static std::string s_misc_config
;
74 struct CompileException
: Exception
{
76 explicit CompileException(A
&&... args
)
77 : Exception(folly::sformat(std::forward
<A
>(args
)...))
81 [[noreturn
]] void throwErrno(const char* what
) {
82 throw CompileException("{}: {}", what
, folly::errnoStr(errno
));
85 CompilerResult
assemble_string_handle_errors(const char* code
,
86 const std::string
& hhas
,
89 const Native::FuncTable
& nativeFuncs
,
91 CompileAbortMode mode
) {
93 return assemble_string(hhas
.c_str(),
98 false); /* swallow errors */
99 } catch (const FatalErrorException
&) {
101 } catch (const TranslationFatal
& ex
) {
102 // Assembler returned an error when building this unit
103 if (mode
>= CompileAbortMode::VerifyErrors
) internal_error
= true;
105 } catch (const AssemblerUnserializationError
& ex
) {
106 // Variable unserializer threw when called from the assembler, treat it
107 // as an internal error.
108 internal_error
= true;
110 } catch (const AssemblerError
& ex
) {
111 if (mode
>= CompileAbortMode::VerifyErrors
) internal_error
= true;
113 if (RuntimeOption::EvalHackCompilerVerboseErrors
) {
114 auto const msg
= folly::sformat(
116 "========== PHP Source ==========\n"
118 "========== HackC Result ==========\n"
124 Logger::FError("HackC Generated a bad unit: {}", msg
);
129 } catch (const std::exception
& ex
) {
130 internal_error
= true;
135 ////////////////////////////////////////////////////////////////////////////////
137 struct ConfigBuilder
{
139 ConfigBuilder
& addField(folly::StringPiece key
, const T
& data
) {
140 if (!m_config
.isObject()) {
141 m_config
= folly::dynamic::object();
144 m_config
[key
] = folly::dynamic::object(
145 "global_value", folly::toDynamic(data
));
150 std::string
toString() const {
151 return m_config
.isNull() ? "" : folly::toJson(m_config
);
155 folly::dynamic m_config
{nullptr};
158 CompilerResult
hackc_compile(
160 const char* filename
,
162 const Native::FuncTable
& nativeFuncs
,
164 bool forDebuggerEval
,
165 bool& internal_error
,
166 const RepoOptionsFlags
& options
,
167 CompileAbortMode mode
169 // Create DeclProvider. Returns nullptr if disabled or too early.
170 auto aliased_namespaces
= options
.getAliasedNamespacesConfig();
171 auto provider
= HhvmDeclProvider::create(options
);
173 uint8_t flags
= make_env_flags(
174 isSystemLib
, // is_systemlib
176 forDebuggerEval
, // for_debugger_eval
177 true, // dump_symbol_refs
178 false // disable_toplevel_elaboration
181 NativeEnv
const native_env
{
182 reinterpret_cast<uint64_t>(provider
.get()),
183 reinterpret_cast<uint64_t>(provider
? &hhvm_decl_provider_get_decl
: nullptr),
187 RuntimeOption::EvalEmitClassPointers
,
188 RuntimeOption::CheckIntOverflow
,
189 options
.getCompilerFlags(),
190 options
.getParserFlags(),
194 // Invoke hackc, producing a rust Vec<u8> containing HHAS.
195 rust::Vec
<uint8_t> hhas_vec
= [&] {
199 return tracing::Props
{}
200 .add("filename", filename
? filename
: "")
201 .add("code_size", strlen(code
));
204 return hackc_compile_from_text_cpp_ffi(native_env
, code
);
206 auto const hhas
= std::string(hhas_vec
.begin(), hhas_vec
.end());
208 // Assemble HHAS into a UnitEmitter, or a std::string if there were errors.
209 auto res
= assemble_string_handle_errors(code
,
217 if (RO::EvalTranslateHackC
) {
218 rust::Box
<HackCUnitWrapper
> unit_wrapped
=
219 hackc_compile_unit_from_text_cpp_ffi(native_env
, code
);
220 rust::Vec
<uint8_t> hhbc
{hackc_unit_to_string_cpp_ffi(native_env
, *unit_wrapped
)};
221 std::string
hhasString(hhbc
.begin(), hhbc
.end());
223 auto const assemblerOut
= [&]() -> std::string
{
224 if (auto ue
= boost::get
<std::unique_ptr
<UnitEmitter
>>(&res
)) {
226 return disassemble((*ue
)->create().get(), true);
228 return boost::get
<std::string
>(res
);
230 const hackc::hhbc::HackCUnit
* unit
= hackCUnitRaw(unit_wrapped
);
232 auto const ue
= unitEmitterFromHackCUnit(*unit
,
237 auto const hackCTranslatorOut
= disassemble(ue
->create().get(), true);
239 if (hackCTranslatorOut
.length() != assemblerOut
.length()) {
240 Logger::FError("HackC Translator incorrect length: {}\n", filename
);
243 UNUSED
auto start_of_line
= 0;
244 for(int i
= 0; i
< hackCTranslatorOut
.length(); i
++) {
245 if (hackCTranslatorOut
[i
] == '\n' || assemblerOut
[i
] == '\n') {
248 if (hackCTranslatorOut
[i
] != assemblerOut
[i
]) {
249 while (i
< hackCTranslatorOut
.length() &&
250 hackCTranslatorOut
[i
] != '\n' && assemblerOut
[i
] != '\n') {
253 Logger::FError("HackC Translator incorrect: {}\n", filename
);
254 ITRACE(3, "HackC Translator: {}\n\nassembler: {}\n\n",
255 hackCTranslatorOut
.substr(start_of_line
,i
),
256 assemblerOut
.substr(start_of_line
,i
)
258 ITRACE(4, "HackC Translator:\n{}\n", hackCTranslatorOut
);
259 ITRACE(4, "assembler:\n{}\n", assemblerOut
);
263 } catch (const TranslationFatal
& ex
) {
264 auto const err
= ex
.what();
265 if (std::strcmp(err
, assemblerOut
.c_str())) {
266 Logger::FError("HackC Translator incorrect error: {}\n", filename
);
268 ITRACE(4, "HackC Translator Err: {}\n", err
);
274 ////////////////////////////////////////////////////////////////////////////////
277 CompilerAbort::CompilerAbort(const std::string
& filename
,
278 const std::string
& error
)
279 : std::runtime_error
{
281 "Encountered an internal error while processing HHAS for {}, "
282 "bailing because Eval.AbortBuildOnCompilerError is set\n\n{}",
289 void compilers_start() {
290 // Some configs, like IncludeRoots, can't easily be Config::Bind(ed), so here
291 // we create a place to dump miscellaneous config values HackC might want.
292 s_misc_config
= []() -> std::string
{
293 if (RuntimeOption::EvalHackCompilerInheritConfig
) {
294 return ConfigBuilder()
295 .addField("hhvm.include_roots", RuntimeOption::IncludeRoots
)
302 ParseFactsResult
extract_facts(
303 const std::string
& filename
,
304 const std::string
& code
,
305 const RepoOptionsFlags
& options
307 auto const get_facts
= [&](const std::string
& source_text
) -> ParseFactsResult
{
309 std::int32_t decl_flags
= options
.getDeclFlags();
310 rust::Box
<DeclParserOptions
> decl_opts
=
311 hackc_create_direct_decl_parse_options(
313 options
.getAliasedNamespacesConfig());
314 DeclResult decls
= hackc_direct_decl_parse(*decl_opts
, filename
, source_text
);
315 FactsResult facts
= hackc_decls_to_facts_cpp_ffi(decl_flags
, decls
, source_text
);
316 rust::String facts_as_json
= hackc_facts_to_json_cpp_ffi(facts
, source_text
);
317 return FactsJSONString
{ std::string(facts_as_json
) };
318 } catch (const std::exception
& e
) {
319 return FactsJSONString
{ "" }; // Swallow errors from HackC
324 return get_facts(code
);
326 auto w
= Stream::getWrapperFromURI(StrNR(filename
));
327 if (!(w
&& dynamic_cast<FileStreamWrapper
*>(w
))) {
328 throwErrno("Failed to extract facts: Could not get FileStreamWrapper.");
330 const auto f
= w
->open(StrNR(filename
), "r", 0, nullptr);
331 if (!f
) throwErrno("Failed to extract facts: Could not read source code.");
332 auto const str
= f
->read();
333 return get_facts(str
.get()->toCppString());
337 FfpResult
ffp_parse_file(
338 const std::string
& contents
,
339 const RepoOptionsFlags
& options
341 auto const env
= options
.getParserEnvironment();
342 auto const parse_tree
= hackc_parse_positioned_full_trivia_cpp_ffi(contents
, env
);
343 return FfpJSONString
{ std::string(parse_tree
) };
346 std::unique_ptr
<UnitCompiler
>
347 UnitCompiler::create(LazyUnitContentsLoader
& loader
,
348 const char* filename
,
349 const Native::FuncTable
& nativeFuncs
,
351 bool forDebuggerEval
) {
352 auto make
= [&loader
, &nativeFuncs
, filename
, isSystemLib
, forDebuggerEval
] {
353 return std::make_unique
<HackcUnitCompiler
>(
362 if (g_unit_emitter_cache_hook
&& !forDebuggerEval
) {
363 return std::make_unique
<CacheUnitCompiler
>(
376 std::unique_ptr
<UnitEmitter
> HackcUnitCompiler::compile(
378 CompileAbortMode mode
) {
382 auto res
= hackc_compile(m_loader
.contents().data(),
391 auto unitEmitter
= match
<std::unique_ptr
<UnitEmitter
>>(
393 [&] (std::unique_ptr
<UnitEmitter
>& ue
) {
395 return std::move(ue
);
397 [&] (std::string
& err
) {
399 case CompileAbortMode::Never
:
401 case CompileAbortMode::AllErrorsNull
: {
402 auto ue
= std::unique_ptr
<UnitEmitter
>{};
406 case CompileAbortMode::OnlyICE
:
407 case CompileAbortMode::VerifyErrors
:
408 case CompileAbortMode::AllErrors
:
409 // run_compiler will promote errors to ICE as appropriate based on mode
410 if (ice
) throw CompilerAbort
{m_filename
, err
};
412 return createFatalUnit(
413 makeStaticString(m_filename
),
421 if (unitEmitter
) unitEmitter
->m_ICE
= ice
;
425 std::unique_ptr
<UnitEmitter
>
426 CacheUnitCompiler::compile(bool& cacheHit
, CompileAbortMode mode
) {
427 assertx(g_unit_emitter_cache_hook
);
429 return g_unit_emitter_cache_hook(
432 m_loader
.fileLength(),
433 [&] (bool wantsICE
) {
434 if (!m_fallback
) m_fallback
= m_makeFallback();
436 return m_fallback
->compile(
438 wantsICE
? mode
: CompileAbortMode::AllErrorsNull
445 ////////////////////////////////////////////////////////////////////////////////