hphp/runtime/vm/unit-parser.cpp (379 lines of code) (raw):

/* +----------------------------------------------------------------------+ | HipHop for PHP | +----------------------------------------------------------------------+ | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) | +----------------------------------------------------------------------+ | This source file is subject to version 3.01 of the PHP license, | | that is bundled with this package in the file LICENSE, and is | | available through the world-wide-web at the following url: | | http://www.php.net/license/3_01.txt | | If you did not receive a copy of the PHP license and are unable to | | obtain it through the world-wide-web, please send a note to | | license@php.net so we can mail you a copy immediately. | +----------------------------------------------------------------------+ */ #include "hphp/runtime/vm/unit-parser.h" #include <cinttypes> #include <condition_variable> #include <fstream> #include <iterator> #include <memory> #include <mutex> #include <signal.h> #include <sstream> #include <stdio.h> #include <string> #include <sys/types.h> #include <sys/wait.h> #include <folly/compression/Zstd.h> #include <folly/DynamicConverter.h> #include <folly/json.h> #include <folly/FileUtil.h> #include <folly/system/ThreadName.h> #include "hphp/hack/src/hackc/ffi_bridge/compiler_ffi.rs" #include "hphp/hack/src/hackc/hhbc-ast.h" #include "hphp/hack/src/parser/ffi_bridge/parser_ffi.rs" #include "hphp/runtime/base/autoload-map.h" #include "hphp/runtime/base/autoload-handler.h" #include "hphp/runtime/base/file-stream-wrapper.h" #include "hphp/runtime/base/ini-setting.h" #include "hphp/runtime/base/stream-wrapper-registry.h" #include "hphp/runtime/base/unit-cache.h" #include "hphp/runtime/vm/disas.h" #include "hphp/runtime/vm/native.h" #include "hphp/runtime/vm/decl-provider.h" #include "hphp/runtime/vm/hackc-translator.h" #include "hphp/runtime/vm/unit-emitter.h" #include "hphp/runtime/vm/unit-gen-helpers.h" #include "hphp/util/atomic-vector.h" #include "hphp/util/embedded-data.h" #include "hphp/util/gzip.h" #include "hphp/util/hackc-log.h" #include "hphp/util/light-process.h" #include "hphp/util/logger.h" #include "hphp/util/match.h" #include "hphp/util/sha1.h" #include "hphp/util/struct-log.h" #include "hphp/util/timer.h" #include "hphp/zend/zend-strtod.h" namespace HPHP { TRACE_SET_MOD(unit_parse); UnitEmitterCacheHook g_unit_emitter_cache_hook = nullptr; static std::string s_misc_config; namespace { struct CompileException : Exception { template<class... A> explicit CompileException(A&&... args) : Exception(folly::sformat(std::forward<A>(args)...)) {} }; [[noreturn]] void throwErrno(const char* what) { throw CompileException("{}: {}", what, folly::errnoStr(errno)); } CompilerResult assemble_string_handle_errors(const char* code, const std::string& hhas, const char* filename, const SHA1& sha1, const Native::FuncTable& nativeFuncs, bool& internal_error, CompileAbortMode mode) { try { return assemble_string(hhas.c_str(), hhas.length(), filename, sha1, nativeFuncs, false); /* swallow errors */ } catch (const FatalErrorException&) { throw; } catch (const TranslationFatal& ex) { // Assembler returned an error when building this unit if (mode >= CompileAbortMode::VerifyErrors) internal_error = true; return ex.what(); } catch (const AssemblerUnserializationError& ex) { // Variable unserializer threw when called from the assembler, treat it // as an internal error. internal_error = true; return ex.what(); } catch (const AssemblerError& ex) { if (mode >= CompileAbortMode::VerifyErrors) internal_error = true; if (RuntimeOption::EvalHackCompilerVerboseErrors) { auto const msg = folly::sformat( "{}\n" "========== PHP Source ==========\n" "{}\n" "========== HackC Result ==========\n" "{}\n", ex.what(), code, hhas ); Logger::FError("HackC Generated a bad unit: {}", msg); return msg; } else { return ex.what(); } } catch (const std::exception& ex) { internal_error = true; return ex.what(); } } //////////////////////////////////////////////////////////////////////////////// struct ConfigBuilder { template<typename T> ConfigBuilder& addField(folly::StringPiece key, const T& data) { if (!m_config.isObject()) { m_config = folly::dynamic::object(); } m_config[key] = folly::dynamic::object( "global_value", folly::toDynamic(data)); return *this; } std::string toString() const { return m_config.isNull() ? "" : folly::toJson(m_config); } private: folly::dynamic m_config{nullptr}; }; CompilerResult hackc_compile( const char* code, const char* filename, const SHA1& sha1, const Native::FuncTable& nativeFuncs, bool isSystemLib, bool forDebuggerEval, bool& internal_error, const RepoOptionsFlags& options, CompileAbortMode mode ) { // Create DeclProvider. Returns nullptr if disabled or too early. auto aliased_namespaces = options.getAliasedNamespacesConfig(); auto provider = HhvmDeclProvider::create(options); uint8_t flags = make_env_flags( isSystemLib, // is_systemlib false, // is_evaled forDebuggerEval, // for_debugger_eval true, // dump_symbol_refs false // disable_toplevel_elaboration ); NativeEnv const native_env{ reinterpret_cast<uint64_t>(provider.get()), reinterpret_cast<uint64_t>(provider ? &hhvm_decl_provider_get_decl : nullptr), filename, aliased_namespaces, s_misc_config, RuntimeOption::EvalEmitClassPointers, RuntimeOption::CheckIntOverflow, options.getCompilerFlags(), options.getParserFlags(), flags }; // Invoke hackc, producing a rust Vec<u8> containing HHAS. rust::Vec<uint8_t> hhas_vec = [&] { tracing::Block _{ "hackc", [&] { return tracing::Props{} .add("filename", filename ? filename : "") .add("code_size", strlen(code)); } }; return hackc_compile_from_text_cpp_ffi(native_env, code); }(); auto const hhas = std::string(hhas_vec.begin(), hhas_vec.end()); // Assemble HHAS into a UnitEmitter, or a std::string if there were errors. auto res = assemble_string_handle_errors(code, hhas, filename, sha1, nativeFuncs, internal_error, mode); if (RO::EvalTranslateHackC) { rust::Box<HackCUnitWrapper> unit_wrapped = hackc_compile_unit_from_text_cpp_ffi(native_env, code); rust::Vec<uint8_t> hhbc {hackc_unit_to_string_cpp_ffi(native_env, *unit_wrapped)}; std::string hhasString(hhbc.begin(), hhbc.end()); auto const assemblerOut = [&]() -> std::string { if (auto ue = boost::get<std::unique_ptr<UnitEmitter>>(&res)) { (*ue)->finish(); return disassemble((*ue)->create().get(), true); } return boost::get<std::string>(res); }(); const hackc::hhbc::HackCUnit* unit = hackCUnitRaw(unit_wrapped); try { auto const ue = unitEmitterFromHackCUnit(*unit, filename, sha1, nativeFuncs, hhasString); auto const hackCTranslatorOut = disassemble(ue->create().get(), true); if (hackCTranslatorOut.length() != assemblerOut.length()) { Logger::FError("HackC Translator incorrect length: {}\n", filename); } UNUSED auto start_of_line = 0; for(int i = 0; i < hackCTranslatorOut.length(); i++) { if (hackCTranslatorOut[i] == '\n' || assemblerOut[i] == '\n') { start_of_line = i; } if (hackCTranslatorOut[i] != assemblerOut[i]) { while (i < hackCTranslatorOut.length() && hackCTranslatorOut[i] != '\n' && assemblerOut[i] != '\n') { i++; } Logger::FError("HackC Translator incorrect: {}\n", filename); ITRACE(3, "HackC Translator: {}\n\nassembler: {}\n\n", hackCTranslatorOut.substr(start_of_line,i), assemblerOut.substr(start_of_line,i) ); ITRACE(4, "HackC Translator:\n{}\n", hackCTranslatorOut); ITRACE(4, "assembler:\n{}\n", assemblerOut); break; } } } catch (const TranslationFatal& ex) { auto const err = ex.what(); if (std::strcmp(err, assemblerOut.c_str())) { Logger::FError("HackC Translator incorrect error: {}\n", filename); } ITRACE(4, "HackC Translator Err: {}\n", err); } } return res; } //////////////////////////////////////////////////////////////////////////////// } CompilerAbort::CompilerAbort(const std::string& filename, const std::string& error) : std::runtime_error{ folly::sformat( "Encountered an internal error while processing HHAS for {}, " "bailing because Eval.AbortBuildOnCompilerError is set\n\n{}", filename, error ) } { } void compilers_start() { // Some configs, like IncludeRoots, can't easily be Config::Bind(ed), so here // we create a place to dump miscellaneous config values HackC might want. s_misc_config = []() -> std::string { if (RuntimeOption::EvalHackCompilerInheritConfig) { return ConfigBuilder() .addField("hhvm.include_roots", RuntimeOption::IncludeRoots) .toString(); } return ""; }(); } ParseFactsResult extract_facts( const std::string& filename, const std::string& code, const RepoOptionsFlags& options ) { auto const get_facts = [&](const std::string& source_text) -> ParseFactsResult { try { std::int32_t decl_flags = options.getDeclFlags(); rust::Box<DeclParserOptions> decl_opts = hackc_create_direct_decl_parse_options( decl_flags, options.getAliasedNamespacesConfig()); DeclResult decls = hackc_direct_decl_parse(*decl_opts, filename, source_text); FactsResult facts = hackc_decls_to_facts_cpp_ffi(decl_flags, decls, source_text); rust::String facts_as_json = hackc_facts_to_json_cpp_ffi(facts, source_text); return FactsJSONString { std::string(facts_as_json) }; } catch (const std::exception& e) { return FactsJSONString { "" }; // Swallow errors from HackC } }; if (!code.empty()) { return get_facts(code); } else { auto w = Stream::getWrapperFromURI(StrNR(filename)); if (!(w && dynamic_cast<FileStreamWrapper*>(w))) { throwErrno("Failed to extract facts: Could not get FileStreamWrapper."); } const auto f = w->open(StrNR(filename), "r", 0, nullptr); if (!f) throwErrno("Failed to extract facts: Could not read source code."); auto const str = f->read(); return get_facts(str.get()->toCppString()); } } FfpResult ffp_parse_file( const std::string& contents, const RepoOptionsFlags& options ) { auto const env = options.getParserEnvironment(); auto const parse_tree = hackc_parse_positioned_full_trivia_cpp_ffi(contents, env); return FfpJSONString { std::string(parse_tree) }; } std::unique_ptr<UnitCompiler> UnitCompiler::create(LazyUnitContentsLoader& loader, const char* filename, const Native::FuncTable& nativeFuncs, bool isSystemLib, bool forDebuggerEval) { auto make = [&loader, &nativeFuncs, filename, isSystemLib, forDebuggerEval] { return std::make_unique<HackcUnitCompiler>( loader, filename, nativeFuncs, isSystemLib, forDebuggerEval ); }; if (g_unit_emitter_cache_hook && !forDebuggerEval) { return std::make_unique<CacheUnitCompiler>( loader, filename, nativeFuncs, isSystemLib, false, std::move(make) ); } else { return make(); } } std::unique_ptr<UnitEmitter> HackcUnitCompiler::compile( bool& cacheHit, CompileAbortMode mode) { auto ice = false; cacheHit = false; auto res = hackc_compile(m_loader.contents().data(), m_filename, m_loader.sha1(), m_nativeFuncs, m_isSystemLib, m_forDebuggerEval, ice, m_loader.options(), mode); auto unitEmitter = match<std::unique_ptr<UnitEmitter>>( res, [&] (std::unique_ptr<UnitEmitter>& ue) { ue->finish(); return std::move(ue); }, [&] (std::string& err) { switch (mode) { case CompileAbortMode::Never: break; case CompileAbortMode::AllErrorsNull: { auto ue = std::unique_ptr<UnitEmitter>{}; ue->finish(); return ue; } case CompileAbortMode::OnlyICE: case CompileAbortMode::VerifyErrors: case CompileAbortMode::AllErrors: // run_compiler will promote errors to ICE as appropriate based on mode if (ice) throw CompilerAbort{m_filename, err}; } return createFatalUnit( makeStaticString(m_filename), m_loader.sha1(), FatalOp::Runtime, err ); } ); if (unitEmitter) unitEmitter->m_ICE = ice; return unitEmitter; } std::unique_ptr<UnitEmitter> CacheUnitCompiler::compile(bool& cacheHit, CompileAbortMode mode) { assertx(g_unit_emitter_cache_hook); cacheHit = true; return g_unit_emitter_cache_hook( m_filename, m_loader.sha1(), m_loader.fileLength(), [&] (bool wantsICE) { if (!m_fallback) m_fallback = m_makeFallback(); assertx(m_fallback); return m_fallback->compile( cacheHit, wantsICE ? mode : CompileAbortMode::AllErrorsNull ); }, m_nativeFuncs ); } //////////////////////////////////////////////////////////////////////////////// }