@override-unit-failures Initial support for <?hh typedefs and shapes

Adds runtime support for non-class typehints.  Typedefs are
introduced using type statements, and autoloaded via a new autoload
map entry.  Shapes are parsed but the structure is currently thrown
away and treated as arrays at runtime.  This extends the NamedEntity
structure to sometimes cache 'NameDefs', which are either Typedef*'s
or Class*'s.  VerifyParamType now has to check for typedefs if an
object fails a class check, or when checking non-Object types against
a non-primitive type name that isn't a class.
Esse commit está contido em:
jdelong
2013-02-07 15:09:17 -08:00
commit de Sara Golemon
commit 6faa7cbea1
103 arquivos alterados com 130278 adições e 125230 exclusões
+44
Ver Arquivo
@@ -90,6 +90,7 @@
#include <compiler/statement/use_trait_statement.h>
#include <compiler/statement/trait_prec_statement.h>
#include <compiler/statement/trait_alias_statement.h>
#include "compiler/statement/typedef_statement.h"
#include <compiler/parser/parser.h>
@@ -1636,6 +1637,10 @@ void EmitterVisitor::visit(FileScopePtr file) {
if (h < allHoistable) allHoistable = h;
break;
}
case Statement::KindOfTypedefStatement:
emitTypedef(e, static_pointer_cast<TypedefStatement>(s));
notMergeOnly = true; // TODO(#2103206): typedefs should be mergable
break;
case Statement::KindOfReturnStatement:
if (mainReturn.m_type != KindOfInvalid) break;
if (notMergeOnly) {
@@ -1939,6 +1944,12 @@ bool EmitterVisitor::visit(ConstructPtr node) {
return ret;
}
void EmitterVisitor::emitFatal(Emitter& e, const char* message) {
const StringData* msg = StringData::GetStaticString(message);
e.String(msg);
e.Fatal(0);
}
bool EmitterVisitor::visitImpl(ConstructPtr node) {
if (!node) return false;
@@ -1951,6 +1962,12 @@ bool EmitterVisitor::visitImpl(ConstructPtr node) {
visitKids(node);
return false;
case Statement::KindOfTypedefStatement: {
emitFatal(e, "Type statements are currently only allowed at "
"the top-level");
return false;
}
case Statement::KindOfContinueStatement:
case Statement::KindOfBreakStatement: {
BreakStatementPtr bs(static_pointer_cast<BreakStatement>(s));
@@ -6050,6 +6067,33 @@ void EmitterVisitor::emitClassUseTrait(PreClassEmitter* pce,
}
}
void EmitterVisitor::emitTypedef(Emitter& e, TypedefStatementPtr td) {
auto kind = !strcasecmp(td->value.c_str(), "array") ? KindOfArray :
!strcasecmp(td->value.c_str(), "int") ? KindOfInt64 :
!strcasecmp(td->value.c_str(), "integer") ? KindOfInt64 :
!strcasecmp(td->value.c_str(), "bool") ? KindOfBoolean :
!strcasecmp(td->value.c_str(), "boolean") ? KindOfBoolean :
!strcasecmp(td->value.c_str(), "string") ? KindOfString :
!strcasecmp(td->value.c_str(), "real") ? KindOfDouble :
!strcasecmp(td->value.c_str(), "float") ? KindOfDouble :
!strcasecmp(td->value.c_str(), "double") ? KindOfDouble :
KindOfObject;
// We have to merge the strings as litstrs to ensure namedentity
// creation.
auto const name = StringData::GetStaticString(td->name);
auto const value = StringData::GetStaticString(td->value);
m_ue.mergeLitstr(name);
m_ue.mergeLitstr(value);
Typedef record;
record.m_name = name;
record.m_value = value;
record.m_kind = kind;
Id id = m_ue.addTypedef(record);
e.DefTypedef(id);
}
PreClass::Hoistable EmitterVisitor::emitClass(Emitter& e, ClassScopePtr cNode,
bool toplevel) {
InterfaceStatementPtr is(
+6
Ver Arquivo
@@ -22,6 +22,7 @@
#include <compiler/statement/use_trait_statement.h>
#include <compiler/statement/trait_prec_statement.h>
#include <compiler/statement/trait_alias_statement.h>
#include "compiler/statement/typedef_statement.h"
#include <runtime/vm/func.h>
#include <runtime/vm/unit.h>
@@ -504,6 +505,10 @@ private:
int defI;
};
private:
void emitFatal(Emitter& e, const char* message);
private:
static const size_t kMinStringSwitchCases = 8;
UnitEmitter& m_ue;
FuncEmitter* m_curFunc;
@@ -642,6 +647,7 @@ public:
void emitBuiltinDefaultArg(Emitter& e, Variant& v, DataType t, int paramId);
PreClass::Hoistable emitClass(Emitter& e, ClassScopePtr cNode,
bool topLevel);
void emitTypedef(Emitter& e, TypedefStatementPtr);
void emitBreakHandler(Emitter& e, Label& brkTarg, Label& cntTarg,
Label& brkHand, Label& cntHand, Id iter = -1,
IterKind itKind = KindOfIter);
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+5
Ver Arquivo
@@ -78,6 +78,7 @@
#include <compiler/statement/use_trait_statement.h>
#include <compiler/statement/trait_prec_statement.h>
#include <compiler/statement/trait_alias_statement.h>
#include "compiler/statement/typedef_statement.h"
#include <compiler/analysis/function_scope.h>
@@ -1579,6 +1580,10 @@ void Parser::onGoto(Token &out, Token &label, bool limited) {
out->stmt = NEW_STMT(GotoStatement, label.text());
}
void Parser::onTypedef(Token& out, const Token& name, const Token& type) {
out->stmt = NEW_STMT(TypedefStatement, name.text(), type.text());
}
void Parser::invalidateGoto(TStatementPtr stmt, GotoError error) {
GotoStatement *gs = (GotoStatement*) stmt;
assert(gs);
+8 -7
Ver Arquivo
@@ -14,13 +14,13 @@
+----------------------------------------------------------------------+
*/
#ifndef __HPHP_PARSER_H__
#define __HPHP_PARSER_H__
#ifndef incl_HPHP_COMPILER_PARSER_H_
#define incl_HPHP_COMPILER_PARSER_H_
#include <runtime/base/util/exceptions.h>
#include <util/parser/parser.h>
#include <compiler/construct.h>
#include <compiler/option.h>
#include "runtime/base/util/exceptions.h"
#include "util/parser/parser.h"
#include "compiler/construct.h"
#include "compiler/option.h"
#ifdef HPHP_PARSER_NS
#undef HPHP_PARSER_NS
@@ -236,6 +236,7 @@ public:
void onClosureParam(Token &out, Token *params, Token &param, bool ref);
void onLabel(Token &out, Token &label);
void onGoto(Token &out, Token &label, bool limited);
void onTypedef(Token& out, const Token& name, const Token& type);
virtual void invalidateGoto(TStatementPtr stmt, GotoError error);
virtual void invalidateLabel(TStatementPtr stmt);
@@ -310,4 +311,4 @@ private:
///////////////////////////////////////////////////////////////////////////////
}}
#endif // __HPHP_PARSER_H__
#endif
+2 -1
Ver Arquivo
@@ -78,7 +78,8 @@ DECLARE_BOOST_TYPES(Statement);
x(LabelStatement), \
x(UseTraitStatement), \
x(TraitPrecStatement), \
x(TraitAliasStatement)
x(TraitAliasStatement), \
x(TypedefStatement)
class Statement : public Construct {
public:
@@ -0,0 +1,63 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- 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 "compiler/statement/typedef_statement.h"
namespace HPHP {
//////////////////////////////////////////////////////////////////////
TypedefStatement::TypedefStatement(
STATEMENT_CONSTRUCTOR_PARAMETERS,
const std::string& name,
const std::string& value)
: Statement(STATEMENT_CONSTRUCTOR_PARAMETER_VALUES(TypedefStatement))
, name(name)
, value(value)
{}
TypedefStatement::~TypedefStatement() {}
StatementPtr TypedefStatement::clone() {
return StatementPtr(new TypedefStatement(*this));
}
//////////////////////////////////////////////////////////////////////
ConstructPtr TypedefStatement::getNthKid(int n) const {
always_assert(0);
}
int TypedefStatement::getKidCount() const {
return 0;
}
void TypedefStatement::setNthKid(int n, ConstructPtr cp) {
always_assert(0);
}
//////////////////////////////////////////////////////////////////////
void TypedefStatement::analyzeProgram(AnalysisResultPtr) {}
void TypedefStatement::inferTypes(AnalysisResultPtr) {}
void TypedefStatement::outputPHP(CodeGenerator& cg, AnalysisResultPtr ar) {
}
//////////////////////////////////////////////////////////////////////
}
+45
Ver Arquivo
@@ -0,0 +1,45 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- 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. |
+----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_COMPILER_TYPEDEF_STATEMENT_H_
#define incl_HPHP_COMPILER_TYPEDEF_STATEMENT_H_
#include <boost/shared_ptr.hpp>
#include <string>
#include "compiler/statement/statement.h"
namespace HPHP {
//////////////////////////////////////////////////////////////////////
struct TypedefStatement : Statement {
explicit TypedefStatement(STATEMENT_CONSTRUCTOR_PARAMETERS,
const std::string& name,
const std::string& value);
~TypedefStatement();
DECLARE_STATEMENT_VIRTUAL_FUNCTIONS;
const std::string name;
const std::string value;
};
typedef boost::shared_ptr<TypedefStatement> TypedefStatementPtr;
//////////////////////////////////////////////////////////////////////
}
#endif
+10
Ver Arquivo
@@ -3498,6 +3498,16 @@ DefCns <litstr id> [C] -> [C]
pushes false. Otherwise defines the constant named %1 to have the value $1,
and pushes true.
DefTypedef <litstr id> [] -> []
Define typedef. Typedefs are a hhvm extension to php that allow
declaring new names for existing types. The unit contains a table
of the typedefs it was compiled with. This instruction looks up the
typedef given by %1 in the table. If there is an existing class or
typedef defined with the same name as %1, this function checks
whether it is compatible with the typedef given by %1, and if it
isn't it raises a fatal error.
13. Miscellaneous instructions
------------------------------
+81
Ver Arquivo
@@ -0,0 +1,81 @@
Notes on the `shapes' feature in Hack:
Shapes in <?hh files are a way for us to add typechecking
incrementally to existing php code that uses php arrays somewhat like
structs.
For example, code like this:
<?php
function dot_product($a, $b) {
return $a['x'] * $b['x'] + $a['y'] * $b['y'];
}
Can be annotated with types to look like this:
<?hh
type Point2D = shape('x' => int, 'y' => int);
function dot_product(Point2D $a, Point2D $b): int {
return $a['x'] * $b['x'] + $a['y'] * $b['y'];
}
This is not a complete design rationale, but contains a few notes to
explain why things are the way they are so far.
Notes:
- Classes with public members (or getters/setters) exist and probably
work fine as a sort of record/struct thing. When people want them
(particularly in newer code), they work.
- This is intended to allow type-checking cases where arrays are
currently being used as aggregates (instead of people using
classes). In order for them to be adopted and do what they are
intended to do, migration has to be as painless as possible (which
is why the object-like structs above are not "enough": no one is
going to switch all of www away from struct-like arrays to that).
- Generally we want most <?hh migration to be primarily about adding
type annotations somewhere in a function signature, not about
significantly changing method bodies.
- To be explicit: this last point means $x['foo'] still needs to be
valid syntax for accessing a shape member to ease conversion in the
short term. For construction sites we are just doing s/array/shape/
when you want them checked as shapes (which will also require at
parse time that the keys are string literals, do not start with
digits, etc).
- People sometimes write code that operates on sub-pieces of these
record-like arrays. This means a traditional (C/C++/Java style)
structure is going to be a little weird to add type annotations
for. You can imagine trying to migrate a function to <?hh that
legitimately take "any array with elements named 'id' and 'name'
that are int and string", and is called with various other
struct-like arrays that have other elements lying around in them. To
make this possible to migrate with minimal pain, we're using
structural subtyping.
- Because they don't work like the "structs" people are used to,
calling it a "struct" seemed potentially confusing.
- With enough code in hack so we know the types of things, it might be
possible to automate migration to a new syntax at access
sites. However, in the short term, we definitely can't use $x->foo
since this is an object property access and the runtime can't
compile it differently based on the type of $x. You might also
consider $x.foo (which might work in <?hh files for reasons I'll
leave out), but again for the foreseeable future we probably want
the same syntax to work in a <?php file, and right now this means
the variable $x converted to a string and concatenated with the
value of the constant foo (which will be the string "foo" if
undefined).
- In the syntax for defining shape *types*, our hands are probably a
little less tied. The current choice was to try to keep it somewhat
similar to the access sites and arrays in general (maybe helps so we
can explain this as "shapes are just special/limited arrays"), and
also to fit in with typedefs and possible syntax for adding enums.
+100
Ver Arquivo
@@ -0,0 +1,100 @@
Notes on the typedef feature in Hack:
In HipHop, <?hh files allow a language extension to define new types
for use in typehinting. The syntax looks like this:
type MyInt = int;
type Point = (int, int);
Or
newtype AbstractInt = int;
This document is not a complete design rationale, but may address a
few questions.
Notes:
- We want to support short names for types that should be compatible
with the underlying representation. The syntax used above is found
in several languages (Haskell, OCaml, F#, Rust, others). Go is
similar, but has no equal sign. C#/Java don't have typedefs so they
don't provide much inspiration. C/C++'s typedef/declaration syntax
isn't appropriate (or particularly reasonable).
- Another use case is making types that abstract away the details of
their underlying implementation. For example:
<?hh
newtype SQLSafeString = string;
function scrub_user_input_for_sql(string $str): SQLSafeString {
// ...
}
Haskell uses "newtype" in this case, but requires a named "value
constructor" which you have to use to turn strings into
SQLSafeString in the first place. OCaml doesn't have this kind of
newtype, but you can use the module system to create abstract types.
C++ has strong typedefs only for integral types, with the syntax
"enum class Foo : int {}", which is clearly not something to
emulate. A language proposal for general strong typedefs has
suggested a "using Foo = private int" syntax, which is probably
worth avoiding based on keyword reuse and being a little strange
(and for that matter not actually part of C++ yet, so it may
change).
The semantics planned here is that a type declared with "newtype" is
exactly the same as one declared with "type" as far as the runtime
is concerned, but Hack will consider it compatible with its
underlying type only in the file that declared it.
- Since we want to use them for signatures on functions, we considered
two choices relating to how typedefs would interact with the
runtime. One option is to "drop" typehints about them the same way
most Hack annotations are dropped. Unfortunately this has an impact
on syntax. Imagine the following function definition:
function is_x_large(Point2D $x): bool { return $x['x'] > 10; }
Since hhvm compiles php files separately, we don't know at bytecode
generation time whether Point2D should be a typehint for a class or
dropped for Hack. One option is to mangle hints for types:
function is_x_large(type Point2D $x): bool { return $x['x'] > 10; }
We figured this syntax (and other variants) might hurt adoption
excessively, and runtime checking of typedefs is also preferable to
wrong behavior.
- Because of this, we need some runtime changes for typedefs. An
identifier in a typehint might be a typedef instead of a class in a
given request.
- We support typedefs from one class to another. For Hack the
motivation is mostly support for typedefs of generics. For example:
newtype LinearModel = Vector<float>;
As far as the runtime is concerned, this just means that the type
LinearModel = Vector. Generic information is currently dropped, and
only checked by Hack.
- Typehints for typedefs only check the "top-level" type for primitive
types. In the case of shapes, this means we only check for now that
the parameter is KindOfArray, and rely on Hack to typecheck the
contents. This is pretty much the best we can do currently: the
runtime doesn't really know much about the "deep" type of most
runtime values.
- We do not support "instanceof SomeTypedef". Rationale: instanceof
is important to perf, and we don't know of an important use case.
Not implementing it yet seems like a safe position, since we could
find a way to add it later if there is a compelling use case.
- You can't allocate a class instance via a typedef name.
Essentially, declaring new types only creates new names for
type---it does not create new "value constructors". This may be
changed later if we find it is useful.
+1 -1
Ver Arquivo
@@ -182,7 +182,7 @@ void ProcessInit() {
#define INIT_SYSTEMLIB_CLASS_FIELD(cls) \
{ \
Class *cls = *Unit::GetNamedEntity(s_##cls.get())->clsList(); \
Class *cls = Unit::GetNamedEntity(s_##cls.get())->clsList(); \
assert(!hhbc_ext_class_count || cls); \
SystemLib::s_##cls##Class = cls; \
}
+10
Ver Arquivo
@@ -61,6 +61,7 @@ StaticString s_static("static");
StaticString s_class("class");
StaticString s_function("function");
StaticString s_constant("constant");
StaticString s_type("type");
StaticString s_failure("failure");
///////////////////////////////////////////////////////////////////////////////
@@ -1370,6 +1371,15 @@ bool AutoloadHandler::autoloadConstant(CStrRef name) {
loadFromMap(name, s_constant, false, ConstantExistsChecker()) != Failure;
}
bool AutoloadHandler::autoloadType(CStrRef name) {
return !m_map.isNull() &&
loadFromMap(name, s_type, true,
[] (CStrRef name) {
return !!VM::Unit::GetNamedEntity(name.get())->getCachedNameDef();
}
) != Failure;
}
/**
* invokeHandler returns true if any autoload handlers were executed,
* false otherwise. When this function returns true, it is the caller's
+1
Ver Arquivo
@@ -582,6 +582,7 @@ public:
bool invokeHandler(CStrRef className, bool forceSplStack = false);
bool autoloadFunc(CStrRef name);
bool autoloadConstant(CStrRef name);
bool autoloadType(CStrRef name);
bool setMap(CArrRef map, CStrRef root);
DECLARE_STATIC_REQUEST_LOCAL(AutoloadHandler, s_instance);
+7 -6
Ver Arquivo
@@ -1092,18 +1092,19 @@ void RuntimeOption::Load(Hdf &config, StringVec *overwrites /* = NULL */,
EnableHipHopExperimentalSyntax =
eval["EnableHipHopExperimentalSyntax"].getBool();
EnableShortTags= eval["EnableShortTags"].getBool(true);
if (EnableShortTags) ScannerType |= Scanner::AllowShortTags;
else ScannerType &= ~Scanner::AllowShortTags;
EnableAspTags = eval["EnableAspTags"].getBool();
if (EnableAspTags) ScannerType |= Scanner::AllowAspTags;
else ScannerType &= ~Scanner::AllowAspTags;
EnableXHP = eval["EnableXHP"].getBool(true);
EnableObjDestructCall = eval["EnableObjDestructCall"].getBool(false);
MaxUserFunctionId = eval["MaxUserFunctionId"].getInt32(2 * 65536);
CheckSymLink = eval["CheckSymLink"].getBool(false);
if (EnableHipHopSyntax) ScannerType |= Scanner::EnableHipHopKeywords;
else ScannerType &= ~Scanner::EnableHipHopKeywords;
if (EnableShortTags) ScannerType |= Scanner::AllowShortTags;
else ScannerType &= ~Scanner::AllowShortTags;
if (EnableAspTags) ScannerType |= Scanner::AllowAspTags;
else ScannerType &= ~Scanner::AllowAspTags;
EnableAlternative = eval["EnableAlternative"].getInt32(0);
EnableFinallyStatement = eval["EnableFinallyStatement"].getBool();
+4 -3
Ver Arquivo
@@ -1470,13 +1470,14 @@ bool f_fb_rename_function(CStrRef orig_func_name, CStrRef new_func_name) {
}
/*
fb_autoload_map($map, $root) specifies a mapping
from classes, functions and constants to the files
that define them. The map has the form:
fb_autoload_map($map, $root) specifies a mapping from classes,
functions, constants, and typedefs to the files that define
them. The map has the form:
array('class' => array('cls' => 'cls_file.php', ...),
'function' => array('fun' => 'fun_file.php', ...),
'constant' => array('con' => 'con_file.php', ...),
'type' => array('type' => 'type_file.php', ...),
'failure' => callable);
If the 'failure' element exists, it will be called if the
+8 -2
Ver Arquivo
@@ -5646,7 +5646,7 @@ inline void OPTBLD_INLINE VMExecutionContext::iopFPushCtorD(PC& pc) {
m_fp->m_func->unit()->lookupNamedEntityPairId(id);
Class* cls = Unit::loadClass(nep.second, nep.first);
if (cls == nullptr) {
raise_error("Undefined class: %s",
raise_error(Strings::UNKNOWN_CLASS,
m_fp->m_func->unit()->lookupLitstrId(id)->data());
}
// Lookup the ctor
@@ -6477,6 +6477,12 @@ inline void OPTBLD_INLINE VMExecutionContext::iopDefCls(PC& pc) {
Unit::defClass(c);
}
inline void OPTBLD_INLINE VMExecutionContext::iopDefTypedef(PC& pc) {
NEXT();
DECODE_IVA(tid);
m_fp->m_func->unit()->defTypedef(tid);
}
static inline void checkThis(ActRec* fp) {
if (!fp->hasThis()) {
raise_error(Strings::FATAL_NULL_THIS);
@@ -7386,7 +7392,7 @@ void VMExecutionContext::requestInit() {
profileRequestStart();
#ifdef DEBUG
Class *cls = *Unit::GetNamedEntity(s_stdclass.get())->clsList();
Class* cls = Unit::GetNamedEntity(s_stdclass.get())->clsList();
assert(cls);
assert(cls == SystemLib::s_stdclassClass);
#endif
+1 -6
Ver Arquivo
@@ -527,12 +527,7 @@ void Class::atomicRelease() {
PreClass* pcls = m_preClass.get();
{
Lock l(Unit::s_classesMutex);
Class *const*cls = pcls->namedEntity()->clsList();
while (*cls != this) {
assert(*cls);
cls = &(*cls)->m_nextClass;
}
*const_cast<Class**>(cls) = m_nextClass;
pcls->namedEntity()->removeClass(this);
}
Treadmill::WorkItem::enqueue(new Treadmill::FreeClassTrigger(this));
return;
+3 -2
Ver Arquivo
@@ -32,6 +32,7 @@
#include <util/range.h>
#include <runtime/vm/fixed_string_map.h>
#include <runtime/vm/indexed_string_map.h>
#include "runtime/vm/named_entity.h"
namespace HPHP {
@@ -311,7 +312,7 @@ class PreClass : public AtomicCountable {
void prettyPrint(std::ostream& out) const;
const NamedEntity* namedEntity() const { return m_namedEntity; }
NamedEntity* namedEntity() const { return m_namedEntity; }
private:
typedef IndexedStringMap<Func*,false,Slot> MethodMap;
@@ -320,7 +321,7 @@ private:
private:
Unit* m_unit;
const NamedEntity* m_namedEntity;
NamedEntity* m_namedEntity;
int m_line1;
int m_line2;
Offset m_offset;
+1
Ver Arquivo
@@ -536,6 +536,7 @@ enum SetOpOp {
O(DefFunc, ONE(IVA), NOV, NOV, NF) \
O(DefCls, ONE(IVA), NOV, NOV, NF) \
O(DefCns, ONE(SA), ONE(CV), ONE(CV), NF) \
O(DefTypedef, ONE(IVA), NOV, NOV, NF) \
O(This, NA, NOV, ONE(CV), NF) \
O(BareThis, ONE(OA), NOV, ONE(CV), NF) \
O(CheckThis, NA, NOV, NOV, NF) \
+127
Ver Arquivo
@@ -0,0 +1,127 @@
/*
+----------------------------------------------------------------------+
| HipHop for PHP |
+----------------------------------------------------------------------+
| Copyright (c) 2010- 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. |
+----------------------------------------------------------------------+
*/
#ifndef incl_HPHP_VM_NAMED_ENTITY_H_
#define incl_HPHP_VM_NAMED_ENTITY_H_
#include <tbb/concurrent_unordered_map.h>
#include <boost/operators.hpp>
#include "runtime/base/complex_types.h"
#include "util/atomic.h"
namespace HPHP { namespace VM {
class Class;
class Typedef;
class Func;
//////////////////////////////////////////////////////////////////////
/*
* A NameDef is a definition of a type for a given name. This
* currently means it is either a Class*, a Typedef*, or null.
*/
struct NameDef : private boost::equality_comparable<NameDef> {
explicit NameDef() : m_p(0) {}
explicit NameDef(Class* c) : m_p(reinterpret_cast<uintptr_t>(c)) {
assert(!(m_p & 0x1));
}
explicit NameDef(Typedef* t)
: m_p(reinterpret_cast<uintptr_t>(t) | 0x1)
{}
Class* asClass() const {
return m_p & 0x1 ? nullptr : reinterpret_cast<Class*>(m_p);
}
Typedef* asTypedef() const {
return m_p & 0x1 ? reinterpret_cast<Typedef*>(m_p - 1) : nullptr;
}
explicit operator bool() const { return !!m_p; }
bool operator==(NameDef other) const {
return m_p == other.m_p;
}
private:
uintptr_t m_p;
};
/*
* NamedEntity represents a user-defined name that may map to
* different objects (NameDefs) in different requests. Classes and
* functions are in separate namespaces, so we have a targetcache
* offset for resolving each.
*
* Classes and typedefs are in the same namespace when we're naming
* types, but different namespaces at sites that allocate classes. If
* a typedef is defined for a given name, we'll cache a NameDef in
* each request at m_cachedNameDefOffset. Classes are always cached
* at m_cachedClassOffset. See TargetCache::allocNameDef.
*/
struct NamedEntity {
explicit NamedEntity()
: m_cachedClassOffset(0)
, m_cachedFuncOffset(0)
, m_cachedNameDefOffset(0)
, m_clsList(nullptr)
{}
unsigned m_cachedClassOffset;
unsigned m_cachedFuncOffset;
unsigned m_cachedNameDefOffset;
void setCachedFunc(Func *f);
Func* getCachedFunc() const;
void setCachedClass(Class* c);
Class* getCachedClass() const;
NameDef getCachedNameDef() const;
void setCachedNameDef(NameDef);
Class* clsList() const {
return m_clsList;
}
// Call while holding s_classesMutex. Add or remove classes from
// the list.
void pushClass(Class* cls);
void removeClass(Class* goner);
private:
Class* m_clsList;
};
//////////////////////////////////////////////////////////////////////
typedef tbb::concurrent_unordered_map<
const StringData*,
NamedEntity,
string_data_hash,
string_data_isame
> NamedEntityMap;
typedef std::pair<const StringData*,const NamedEntity*> NamedEntityPair;
//////////////////////////////////////////////////////////////////////
}}
#endif
+28 -12
Ver Arquivo
@@ -1850,16 +1850,28 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) {
m_tb->gen(VerifyParamCallable, locVal, cns(paramId));
return;
}
// For non-object guards, or object guards with non-object runtime
// types, we rely on what we know from the tracelet guards and never
// have to do runtime checks.
if ((!tc.isObject() && !tc.check(locType.toDataType())) ||
(tc.isObject() && !locType.subtypeOf(Type::Obj))) {
spillStack();
m_tb->gen(VerifyParamFail, cns(paramId));
// For non-object guards, we rely on what we know from the tracelet
// guards and never have to do runtime checks.
if (!tc.isObjectOrTypedef()) {
if (!tc.checkPrimitive(locType.toDataType())) {
spillStack();
m_tb->gen(VerifyParamFail, cns(paramId));
return;
}
return;
}
if (!tc.isObject()) {
/*
* If the parameter is an object, we check the object in one of
* various ways (similar to instance of). If the parameter is not
* an object, it still might pass the VerifyParamType if the
* constraint is a typedef.
*
* For now we just interp that case.
*/
if (!locType.isObj()) {
emitInterpOneOrPunt(Type::None);
return;
}
@@ -1897,9 +1909,9 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) {
locVal = m_tb->gen(Unbox, getExitTrace(), locVal);
SSATmp* objClass = m_tb->gen(LdObjClass, locVal);
if (haveBit || classIsUniqueNormalClass(knownConstraint)) {
SSATmp* isInstance = haveBit ?
m_tb->gen(InstanceOfBitmask, objClass, cns(clsName))
: m_tb->gen(ExtendsClass, objClass, constraint);
SSATmp* isInstance = haveBit
? m_tb->gen(InstanceOfBitmask, objClass, cns(clsName))
: m_tb->gen(ExtendsClass, objClass, constraint);
m_tb->ifThen(getCurFunc(),
[&](Block* taken) {
m_tb->gen(JmpZero, taken, isInstance);
@@ -1912,7 +1924,11 @@ void HhbcTranslator::emitVerifyParamType(int32_t paramId) {
);
} else {
spillStack();
m_tb->gen(VerifyParamCls, objClass, constraint, cns(paramId));
m_tb->gen(VerifyParamCls,
objClass,
constraint,
cns(paramId),
cns(uintptr_t(&tc)));
}
}
+4 -1
Ver Arquivo
@@ -334,7 +334,10 @@ O(DefLabel, DMulti, SUnk, E) \
O(Marker, ND, NA, E) \
O(DefFP, D(StkPtr), NA, E) \
O(DefSP, D(StkPtr), S(StkPtr) C(Int), E) \
O(VerifyParamCls, ND, S(Cls) S(Cls) C(Int),E|N|Mem|Refs|Er) \
O(VerifyParamCls, ND, S(Cls) \
S(Cls) \
C(Int) \
C(Int), E|N|Mem|Refs|Er) \
O(VerifyParamCallable, ND, S(Cell) C(Int), E|N|Mem|Refs|Er) \
O(VerifyParamFail, ND, C(Int), E|N|Mem|Refs|Er) \
O(RaiseUninitLoc, ND, S(Str), E|N|Mem|Refs|Er) \
+1 -1
Ver Arquivo
@@ -78,7 +78,7 @@ static CallMap s_callMap({
{PrintInt, (TCA)print_int, DNone, SNone, {{SSA, 0}}},
{PrintBool, (TCA)print_boolean, DNone, SNone, {{SSA, 0}}},
{VerifyParamCls, (TCA)VerifyParamTypeSlow, DNone, SSync,
{{SSA, 0}, {SSA, 1}, {SSA, 2}}},
{{SSA, 0}, {SSA, 1}, {SSA, 2}, {SSA, 3}}},
{VerifyParamCallable, (TCA)VerifyParamTypeCallable, DNone, SSync,
{{TV, 0}, {SSA, 1}}},
{VerifyParamFail, (TCA)VerifyParamTypeFail, DNone, SSync, {{SSA, 0}}},
+8
Ver Arquivo
@@ -717,6 +717,14 @@ CacheHandle allocFixedFunction(const StringData* name) {
return allocFixedFunction(Unit::GetNamedEntity(name), false);
}
CacheHandle allocNameDef(const NamedEntity* ne) {
if (ne->m_cachedNameDefOffset) {
return ne->m_cachedNameDefOffset;
}
return allocFuncOrClass(&ne->m_cachedNameDefOffset,
false /* persistent */); // TODO(#2103214): support persistent
}
template<bool checkOnly>
Class*
lookupKnownClass(Class** cache, const StringData* clsName, bool isClass) {
+13
Ver Arquivo
@@ -304,9 +304,22 @@ Class* lookupKnownClass(Class** cache, const StringData* clsName,
CacheHandle allocClassInitProp(const StringData* name);
CacheHandle allocClassInitSProp(const StringData* name);
/*
* Functions.
*/
CacheHandle allocFixedFunction(const NamedEntity* ne, bool persistent);
CacheHandle allocFixedFunction(const StringData* name);
/*
* NameDefs.
*
* Request-private values for typedef names. When a typedef is
* defined, a NameDef for it is cached here. If it is a typedef for a
* class, the NameDef will be for the target class. Otherwise it's a
* NameDef pointing to the Typedef*.
*/
CacheHandle allocNameDef(const NamedEntity* name);
/*
* Constants.
*
@@ -82,10 +82,32 @@ void VerifyParamTypeCallable(TypedValue value, int param) {
}
HOT_FUNC_VM
void VerifyParamTypeSlow(const Class* cls, const Class* constraint, int param) {
if (UNLIKELY(!(constraint && cls->classof(constraint)))) {
VerifyParamTypeFail(param);
void VerifyParamTypeSlow(const Class* cls,
const Class* constraint,
int param,
const TypeConstraint* expected) {
if (LIKELY(constraint && cls->classof(constraint))) {
return;
}
// Check a typedef for a class. We interp'd if the param wasn't an
// object, so if it's a typedef for something non-objecty we're
// failing anyway.
if (auto namedEntity = expected->namedEntity()) {
NameDef def = namedEntity->getCachedNameDef();
if (UNLIKELY(!def)) {
VMRegAnchor _;
String nameStr(const_cast<StringData*>(expected->typeName()));
if (AutoloadHandler::s_instance->autoloadType(nameStr)) {
def = namedEntity->getCachedNameDef();
}
}
if (def && (constraint = def.asClass()) && cls->classof(constraint)) {
return;
}
}
VerifyParamTypeFail(param);
}
} } }
+4 -1
Ver Arquivo
@@ -61,7 +61,10 @@ void raisePropertyOnNonObject();
void raiseUndefProp(ObjectData* base, const StringData* name);
void VerifyParamTypeFail(int param);
void VerifyParamTypeCallable(TypedValue value, int param);
void VerifyParamTypeSlow(const Class* cls, const Class* constraint, int param);
void VerifyParamTypeSlow(const Class* cls,
const Class* constraint,
int param,
const TypeConstraint* expected);
void raise_error_sd(const StringData* sd);
+7 -5
Ver Arquivo
@@ -10288,7 +10288,7 @@ void
TranslatorX64::analyzeVerifyParamType(Tracelet& t, NormalizedInstruction& i) {
int param = i.imm[0].u_IVA;
const TypeConstraint& tc = curFunc()->params()[param].typeConstraint();
if (!tc.isObject()) {
if (!tc.isObjectOrTypedef()) {
// We are actually using the translation-time value of this local as a
// prediction; if the param check failed at compile-time, we predict it
// will continue failing.
@@ -10387,18 +10387,20 @@ TranslatorX64::translateVerifyParamType(const Tracelet& t,
Stats::emitInc(a, Stats::Tx64_VerifyParamTypeFast);
emitInstanceCheck(t, i, clsName, constraint, inCls, cls, dummy);
} else {
// Compare this class to the incoming object's class. If the typehint's
// class is not present, can not be an instance: fail
// Compare this class to the incoming object's class. If the
// typehint's class is not present, can not be an instance, unless
// this is a typedef. The slow path handles that case.
Stats::emitInc(a, Stats::Tx64_VerifyParamTypeSlowShortcut);
a. cmp_reg64_reg64(r(inCls), r(cls));
{
JccBlock<CC_E> subclassCheck(a);
// Call helper since ObjectData::instanceof is a member function
if (false) {
VerifyParamTypeSlow(constraint, constraint, param);
VerifyParamTypeSlow(constraint, constraint, param, &tc);
}
EMIT_RCALL(a, i, VerifyParamTypeSlow, R(inCls), R(cls),
IMM(param));
IMM(param),
IMM(uintptr_t(&tc)));
}
}
}
+3 -2
Ver Arquivo
@@ -567,8 +567,8 @@ predictOutputs(const Tracelet& t,
const NamedEntityPair& cne =
curFrame()->m_func->unit()->lookupNamedEntityPairId(ni->imm[1].u_SA);
StringData* cnsName = curUnit()->lookupLitstrId(ni->imm[0].u_SA);
Class* cls = *cne.second->clsList();
if (cls && (cls = cls->getCached())) {
Class* cls = cne.second->getCachedClass();
if (cls) {
DataType dt = cls->clsCnsType(cnsName);
if (dt != KindOfUninit) {
ni->outputPredicted = true;
@@ -1168,6 +1168,7 @@ static const struct {
{ OpReqDoc, {Stack1, Stack1, OutUnknown, 0 }},
{ OpEval, {Stack1, Stack1, OutUnknown, 0 }},
{ OpDefFunc, {None, None, OutNone, 0 }},
{ OpDefTypedef, {None, None, OutNone, 0 }},
{ OpDefCls, {None, None, OutNone, 0 }},
{ OpDefCns, {Stack1, Stack1, OutBoolean, 0 }},
+3
Ver Arquivo
@@ -17,10 +17,13 @@
#ifndef incl_TREADMILL_H_
#define incl_TREADMILL_H_
#include "runtime/vm/unit.h"
namespace HPHP {
namespace VM {
class Class;
class Typedef;
namespace Treadmill {
+56 -6
Ver Arquivo
@@ -77,6 +77,7 @@ TypeConstraint::TypeConstraint(const StringData* typeName /* = NULL */,
this, typeName->data());
m_type = { KindOfObject, Precise };
m_namedEntity = Unit::GetNamedEntity(typeName);
TRACE(5, "TypeConstraint: NamedEntity: %p\n", m_namedEntity);
return;
}
if (RuntimeOption::EnableHipHopSyntax || dtype.m_dt == KindOfArray ||
@@ -90,6 +91,47 @@ TypeConstraint::TypeConstraint(const StringData* typeName /* = NULL */,
assert(IMPLIES(isSelf(), m_type.m_dt == KindOfObject));
}
/*
* Note:
*
* We don't need to autoload classes because you can't have an
* instance of a class if it's not defined. However, we need to
* autoload typedefs because they can affect whether the
* VerifyParamType would succeed.
*/
static NameDef getNameDefWithAutoload(const NamedEntity* ne,
const StringData* name) {
NameDef def = ne->getCachedNameDef();
if (!def) {
String nameStr(const_cast<StringData*>(name));
if (!AutoloadHandler::s_instance->autoloadType(nameStr)) {
return NameDef();
}
def = ne->getCachedNameDef();
}
return def;
}
bool TypeConstraint::checkTypedefNonObj(const TypedValue* tv) const {
assert(tv->m_type != KindOfObject); // this checks when tv is not an object
assert(!isSelf() && !isParent());
NameDef def = getNameDefWithAutoload(m_namedEntity, m_typeName);
if (!def) return false;
Typedef* td = def.asTypedef();
return td && equivDataTypes(td->m_kind, tv->m_type);
}
bool TypeConstraint::checkTypedefObj(const TypedValue* tv) const {
assert(tv->m_type == KindOfObject); // this checks when tv is an object
assert(!isSelf() && !isParent() && !isCallable());
NameDef def = getNameDefWithAutoload(m_namedEntity, m_typeName);
if (!def) return false;
Class* c = def.asClass();
return c && tv->m_data.pobj->instanceof(c);
}
bool
TypeConstraint::check(const TypedValue* tv, const Func* func) const {
assert(exists());
@@ -101,7 +143,7 @@ TypeConstraint::check(const TypedValue* tv, const Func* func) const {
if (m_nullable && IS_NULL_TYPE(tv->m_type)) return true;
if (tv->m_type == KindOfObject) {
if (!isObject()) return false;
if (!isObjectOrTypedef()) return false;
// Perfect match seems common enough to be worth skipping the hash
// table lookup.
if (m_typeName->isame(tv->m_data.pobj->getVMClass()->name())) {
@@ -109,12 +151,14 @@ TypeConstraint::check(const TypedValue* tv, const Func* func) const {
return true;
}
const Class *c = nullptr;
if (isSelf() || isParent() || isCallable()) {
const bool selfOrParentOrCallable = isSelf() || isParent() || isCallable();
if (selfOrParentOrCallable) {
if (isSelf()) {
selfToClass(func, &c);
} else if (isParent()) {
parentToClass(func, &c);
} else if (isCallable()) {
} else {
assert(isCallable());
return f_is_callable(tvAsCVarRef(tv));
}
} else {
@@ -126,13 +170,19 @@ TypeConstraint::check(const TypedValue* tv, const Func* func) const {
if (shouldProfile() && c) {
Class::profileInstanceOf(c->preClass()->name());
}
return c && tv->m_data.pobj->instanceof(c);
if (c && tv->m_data.pobj->instanceof(c)) {
return true;
}
return !selfOrParentOrCallable && checkTypedefObj(tv);
}
return equivDataTypes(m_type.m_dt, tv->m_type);
return isObjectOrTypedef() && !isCallable()
? checkTypedefNonObj(tv)
: equivDataTypes(m_type.m_dt, tv->m_type);
}
bool
TypeConstraint::check(DataType dt) const {
TypeConstraint::checkPrimitive(DataType dt) const {
assert(m_type.m_dt != KindOfObject);
assert(dt != KindOfRef);
if (m_nullable && IS_NULL_TYPE(dt)) return true;
+14 -2
Ver Arquivo
@@ -52,6 +52,10 @@ protected:
}
};
// m_type represents the DataType to check on. We don't know
// whether a bare name is a class/interface name or a typedef, so
// when this is set to KindOfObject we may have to look up a typedef
// name and test for a different DataType.
Type m_type;
bool m_nullable;
const StringData* m_typeName;
@@ -68,6 +72,7 @@ public:
bool exists() const { return m_typeName; }
const StringData* typeName() const { return m_typeName; }
const NamedEntity* namedEntity() const { return m_namedEntity; }
bool nullable() const { return m_nullable; }
@@ -83,7 +88,7 @@ public:
return m_type.isCallable();
}
bool isObject() const {
bool isObjectOrTypedef() const {
assert(IMPLIES(isParent(), m_type.m_dt == KindOfObject));
assert(IMPLIES(isSelf(), m_type.m_dt == KindOfObject));
assert(IMPLIES(isCallable(), m_type.m_dt == KindOfObject));
@@ -111,8 +116,15 @@ public:
(IS_NULL_TYPE(t1) && IS_NULL_TYPE(t2));
}
// General check for any constraint.
bool check(const TypedValue* tv, const Func* func) const;
bool check(DataType dt) const;
// Check a constraint when !isObjectOrTypedef().
bool checkPrimitive(DataType dt) const;
// Typedef checks when we know tv is or is not an object.
bool checkTypedefObj(const TypedValue* tv) const;
bool checkTypedefNonObj(const TypedValue* tv) const;
// NB: will throw if the check fails.
void verify(const TypedValue* tv,
+209 -61
Ver Arquivo
@@ -58,7 +58,7 @@ Mutex Unit::s_classesMutex;
*/
static NamedEntityMap *s_namedDataMap;
const NamedEntity* Unit::GetNamedEntity(const StringData *str) {
NamedEntity* Unit::GetNamedEntity(const StringData *str) {
if (!s_namedDataMap) s_namedDataMap = new NamedEntityMap();
NamedEntityMap::const_iterator it = s_namedDataMap->find(str);
if (it != s_namedDataMap->end()) return &it->second;
@@ -71,6 +71,7 @@ const NamedEntity* Unit::GetNamedEntity(const StringData *str) {
}
void NamedEntity::setCachedFunc(Func* f) {
assert(m_cachedFuncOffset);
*(Func**)Transl::TargetCache::handleToPtr(m_cachedFuncOffset) = f;
}
@@ -81,6 +82,45 @@ Func* NamedEntity::getCachedFunc() const {
return nullptr;
}
void NamedEntity::setCachedClass(Class* f) {
assert(m_cachedClassOffset);
*(Class**)Transl::TargetCache::handleToPtr(m_cachedClassOffset) = f;
}
Class* NamedEntity::getCachedClass() const {
if (LIKELY(m_cachedClassOffset != 0)) {
return *(Class**)Transl::TargetCache::handleToPtr(m_cachedClassOffset);
}
return nullptr;
}
void NamedEntity::setCachedNameDef(NameDef nd) {
assert(m_cachedNameDefOffset);
Transl::TargetCache::handleToRef<NameDef>(m_cachedNameDefOffset) = nd;
}
NameDef NamedEntity::getCachedNameDef() const {
if (LIKELY(m_cachedNameDefOffset != 0)) {
return Transl::TargetCache::handleToRef<NameDef>(m_cachedNameDefOffset);
}
return NameDef();
}
void NamedEntity::pushClass(Class* cls) {
assert(!cls->m_nextClass);
cls->m_nextClass = m_clsList;
atomic_release_store(&m_clsList, cls); // TODO(#2054448): ARMv8
}
void NamedEntity::removeClass(Class* goner) {
Class** cls = &m_clsList; // TODO(#2054448): ARMv8
while (*cls != goner) {
assert(*cls);
cls = &(*cls)->m_nextClass;
}
*cls = goner->m_nextClass;
}
UnitMergeInfo* UnitMergeInfo::alloc(size_t size) {
UnitMergeInfo* mi = (UnitMergeInfo*)malloc(
sizeof(UnitMergeInfo) + size * sizeof(void*));
@@ -112,7 +152,7 @@ Array Unit::getUserFunctions() {
AllClasses::AllClasses()
: m_next(s_namedDataMap->begin())
, m_end(s_namedDataMap->end())
, m_current(m_next != m_end ? *m_next->second.clsList() : nullptr) {
, m_current(m_next != m_end ? m_next->second.clsList() : nullptr) {
if (!empty()) skip();
}
@@ -121,7 +161,7 @@ void AllClasses::skip() {
assert(!empty());
++m_next;
while (!empty()) {
m_current = *m_next->second.clsList();
m_current = m_next->second.clsList();
if (m_current) break;
++m_next;
}
@@ -156,16 +196,17 @@ class AllCachedClasses {
void skip() {
Class* cls;
while (!empty()) {
cls = *m_next->second.clsList();
cls = m_next->second.clsList();
if (cls && cls->getCached()) break;
++m_next;
}
}
public:
AllCachedClasses()
: m_next(s_namedDataMap->begin())
, m_end(s_namedDataMap->end()) {
AllCachedClasses()
: m_next(s_namedDataMap->begin())
, m_end(s_namedDataMap->end())
{
skip();
}
bool empty() const {
@@ -173,7 +214,7 @@ AllCachedClasses()
}
Class* front() {
assert(!empty());
Class* c = *m_next->second.clsList();
Class* c = m_next->second.clsList();
assert(c);
c = c->getCached();
assert(c);
@@ -308,16 +349,13 @@ Unit::~Unit() {
// it is possible for Class'es to outlive their Unit.
for (int i = m_preClasses.size(); i--; ) {
PreClass* pcls = m_preClasses[i].get();
Class * const* clsh = pcls->namedEntity()->clsList();
if (clsh) {
Class *cls = *clsh;
while (cls) {
Class* cur = cls;
cls = cls->m_nextClass;
if (cur->preClass() == pcls) {
if (!cur->decAtomicCount()) {
cur->atomicRelease();
}
Class* cls = pcls->namedEntity()->clsList();
while (cls) {
Class* cur = cls;
cls = cls->m_nextClass;
if (cur->preClass() == pcls) {
if (!cur->decAtomicCount()) {
cur->atomicRelease();
}
}
}
@@ -431,26 +469,42 @@ class FrameRestore {
Class* Unit::defClass(const PreClass* preClass,
bool failIsFatal /* = true */) {
// TODO(#2054448): ARMv8
Class* const* clsList = preClass->namedEntity()->clsList();
Class* top = *clsList;
if (top) {
Class *cls = top->getCached();
if (cls) {
// Raise a fatal unless the existing class definition is identical to the
// one this invocation would create.
if (cls->preClass() != preClass) {
if (failIsFatal) {
FrameRestore fr(preClass);
raise_error("Class already declared: %s", preClass->name()->data());
}
return nullptr;
}
return cls;
}
}
// Get a compatible Class, and add it to the list of defined classes.
NamedEntity* const nameList = preClass->namedEntity();
Class* top = nameList->clsList();
/*
* Check if there is already a name defined in this request for this
* NamedEntity.
*
* Raise a fatal unless the existing class definition is identical to the
* one this invocation would create.
*/
if (NameDef current = nameList->getCachedNameDef()) {
auto name = current.asTypedef()
? current.asTypedef()->m_name
: current.asClass()->name();
FrameRestore fr(preClass);
raise_error("Cannot declare class with the same name (%s) as an "
"existing type", name->data());
return nullptr;
}
// If it's compatible, the class must have been declared as a
// DefClass, not a typedef. So we don't need to check the NameDef
// for a class, only the cached class offset.
if (Class* cls = nameList->getCachedClass()) {
if (cls->preClass() != preClass) {
if (failIsFatal) {
FrameRestore fr(preClass);
raise_error("Class already declared: %s", preClass->name()->data());
}
return nullptr;
}
return cls;
}
// Get a compatible Class, and add it to the list of defined classes.
Class* parent = nullptr;
for (;;) {
// Search for a compatible extant class. Searching from most to least
@@ -493,27 +547,26 @@ Class* Unit::defClass(const PreClass* preClass,
newClass = Class::newClass(const_cast<PreClass*>(preClass), parent);
}
Lock l(Unit::s_classesMutex);
/*
We could re-enter via Unit::getClass() or class_->avail(), so
no need for *clsList to be volatile
We could re-enter via Unit::getClass() or class_->avail().
*/
if (UNLIKELY(top != *clsList)) {
top = *clsList;
if (UNLIKELY(top != nameList->clsList())) {
top = nameList->clsList();
continue;
}
if (top) {
newClass->m_cachedOffset = top->m_cachedOffset;
} else {
newClass->m_cachedOffset =
Transl::TargetCache::allocKnownClass(newClass.get());
if (!nameList->m_cachedClassOffset) {
nameList->m_cachedClassOffset = Transl::TargetCache::
allocKnownClass(newClass.get());
}
newClass->m_nextClass = top;
newClass->m_cachedOffset = nameList->m_cachedClassOffset;
if (Class::s_instanceBitsInit.load(std::memory_order_acquire)) {
// If the instance bitmap has already been set up, we can just initialize
// our new class's bits and add ourselves to the class list normally.
newClass->setInstanceBits();
atomic_release_store(const_cast<Class**>(clsList), newClass.get());
nameList->pushClass(newClass.get());
} else {
// Otherwise, we have to grab the read lock. If the map has been
// initialized since we checked, initialize the bits normally. If not, we
@@ -523,7 +576,7 @@ Class* Unit::defClass(const PreClass* preClass,
if (Class::s_instanceBitsInit.load(std::memory_order_acquire)) {
newClass->setInstanceBits();
}
atomic_release_store(const_cast<Class**>(clsList), newClass.get());
nameList->pushClass(newClass.get());
}
newClass.get()->incAtomicCount();
newClass.get()->setCached();
@@ -532,6 +585,84 @@ Class* Unit::defClass(const PreClass* preClass,
}
}
void Unit::defTypedef(Id id) {
assert(id < m_typedefs.size());
auto thisType = &m_typedefs[id];
auto nameList = GetNamedEntity(thisType->m_name);
auto checkExistingClass = [&] (Class* cls) {
if (thisType->m_kind != KindOfObject ||
!cls->name()->isame(thisType->m_value)) {
raise_error("The type %s is already defined to a different class (%s)",
thisType->m_name->data(),
cls->name()->data());
}
};
/*
* Check if this name already has a NameDef, and if so make sure it
* is compatible.
*/
if (NameDef current = nameList->getCachedNameDef()) {
if (Class* cls = current.asClass()) {
checkExistingClass(cls);
return;
}
Typedef* td = current.asTypedef();
assert(td);
if (thisType->m_kind != td->m_kind ||
!td->m_value->isame(thisType->m_value)) {
raise_error("The type %s is already defined to an incompatible type",
thisType->m_name->data());
}
return;
}
// There might also be a class with this name already.
if (Class* cls = nameList->getCachedClass()) {
checkExistingClass(cls);
return;
}
if (!nameList->m_cachedNameDefOffset) {
nameList->m_cachedNameDefOffset =
Transl::TargetCache::allocNameDef(nameList);
}
/*
* The cached NameDef for this typedef will be the actual Class* if
* it is a typedef for a class type, otherwise it is a pointer to a
* Typedef structure.
*
* If this typedef is a KindOfObject and the name on the right hand
* side was another typedef, we will bind the name to the other side
* for this request. We need to inspect the right hand side and
* figure out what it was first.
*/
if (thisType->m_kind != KindOfObject) {
nameList->setCachedNameDef(NameDef(thisType));
return;
}
if (auto klass = Unit::loadClass(thisType->m_value)) {
nameList->setCachedNameDef(NameDef(klass));
return;
}
auto targetNameList = GetNamedEntity(thisType->m_value);
NameDef target = targetNameList->getCachedNameDef();
if (!target) {
AutoloadHandler::s_instance->autoloadType(thisType->m_value->data());
target = targetNameList->getCachedNameDef();
if (!target) {
raise_error("Unknown type or class %s", thisType->m_value->data());
return;
}
}
assert(target);
nameList->setCachedNameDef(target);
}
void Unit::renameFunc(const StringData* oldName, const StringData* newName) {
// renameFunc() should only be used by VMExecutionContext::createFunction.
// We do a linear scan over all the functions in the unit searching for the
@@ -554,10 +685,9 @@ void Unit::renameFunc(const StringData* oldName, const StringData* newName) {
Class* Unit::loadClass(const NamedEntity* ne,
const StringData* name) {
Class *cls = *ne->clsList();
if (LIKELY(cls != nullptr)) {
cls = cls->getCached();
if (LIKELY(cls != nullptr)) return cls;
Class* cls;
if (LIKELY((cls = ne->getCachedClass()) != nullptr)) {
return cls;
}
VMRegAnchor _;
AutoloadHandler::s_instance->invokeHandler(
@@ -604,6 +734,7 @@ void Unit::initialMerge() {
if (LIKELY(m_mergeState == UnitMergeStateUnmerged)) {
int state = 0;
m_mergeState = UnitMergeStateMerging;
bool allFuncsUnique = RuntimeOption::RepoAuthoritative;
for (MutableFuncRange fr(nonMainFuncs()); !fr.empty();) {
Func* f = fr.popFront();
@@ -772,7 +903,7 @@ size_t compactUnitMergeInfo(UnitMergeInfo* in, UnitMergeInfo* out) {
void* obj = in->mergeableObj(ix);
assert((uintptr_t(obj) & 1) == 0);
PreClass* pre = (PreClass*)obj;
Class* cls = *pre->namedEntity()->clsList();
Class* cls = pre->namedEntity()->clsList();
assert(cls && !cls->m_nextClass);
assert(cls->preClass() == pre);
if (TargetCache::isPersistentHandle(cls->m_cachedOffset)) {
@@ -793,7 +924,7 @@ size_t compactUnitMergeInfo(UnitMergeInfo* in, UnitMergeInfo* out) {
switch (k) {
case UnitMergeKindClass: {
PreClass* pre = (PreClass*)obj;
Class* cls = *pre->namedEntity()->clsList();
Class* cls = pre->namedEntity()->clsList();
assert(cls && !cls->m_nextClass);
assert(cls->preClass() == pre);
if (TargetCache::isPersistentHandle(cls->m_cachedOffset)) {
@@ -1310,7 +1441,7 @@ void Unit::enableIntercepts() {
Lock lock(s_classesMutex);
for (int i = m_preClasses.size(); i--; ) {
PreClass* pcls = m_preClasses[i].get();
Class *cls = *pcls->namedEntity()->clsList();
Class* cls = pcls->namedEntity()->clsList();
while (cls) {
/*
* verify that this class corresponds to the
@@ -1405,7 +1536,7 @@ void UnitRepoProxy::createSchema(int repoId, RepoTxn& txn) {
ssCreate << "CREATE TABLE " << m_repo.table(repoId, "Unit")
<< "(unitSn INTEGER PRIMARY KEY, md5 BLOB, bc BLOB,"
" bc_meta BLOB, mainReturn BLOB, mergeable INTEGER,"
"lines BLOB, UNIQUE (md5));";
"lines BLOB, typedefs BLOB, UNIQUE (md5));";
txn.exec(ssCreate.str());
}
{
@@ -1488,14 +1619,16 @@ void UnitRepoProxy::InsertUnitStmt
const uchar* bc, size_t bclen,
const uchar* bc_meta, size_t bc_meta_len,
const TypedValue* mainReturn, bool mergeOnly,
const LineTable& lines) {
const LineTable& lines,
const std::vector<Typedef>& typedefs) {
BlobEncoder linesBlob;
BlobEncoder typedefsBlob;
if (!prepared()) {
std::stringstream ssInsert;
ssInsert << "INSERT INTO " << m_repo.table(m_repoId, "Unit")
<< " VALUES(NULL, @md5, @bc, @bc_meta,"
" @mainReturn, @mergeable, @lines);";
" @mainReturn, @mergeable, @lines, @typedefs);";
txn.prepare(*this, ssInsert.str());
}
RepoTxnQuery query(txn, *this);
@@ -1507,6 +1640,7 @@ void UnitRepoProxy::InsertUnitStmt
query.bindTypedValue("@mainReturn", *mainReturn);
query.bindBool("@mergeable", mergeOnly);
query.bindBlob("@lines", linesBlob(lines), /* static */ true);
query.bindBlob("@typedefs", typedefsBlob(typedefs), /* static */ true);
query.exec();
unitSn = query.getInsertedRowid();
}
@@ -1517,7 +1651,8 @@ bool UnitRepoProxy::GetUnitStmt
RepoTxn txn(m_repo);
if (!prepared()) {
std::stringstream ssSelect;
ssSelect << "SELECT unitSn,bc,bc_meta,mainReturn,mergeable,lines FROM "
ssSelect << "SELECT unitSn,bc,bc_meta,mainReturn,mergeable,"
"lines,typedefs FROM "
<< m_repo.table(m_repoId, "Unit")
<< " WHERE md5 == @md5;";
txn.prepare(*this, ssSelect.str());
@@ -1535,6 +1670,7 @@ bool UnitRepoProxy::GetUnitStmt
TypedValue value; /**/ query.getTypedValue(3, value);
bool mergeable; /**/ query.getBool(4, mergeable);
BlobDecoder linesBlob = /**/ query.getBlob(5);
BlobDecoder typedefsBlob = /**/ query.getBlob(6);
ue.setRepoId(m_repoId);
ue.setSn(unitSn);
ue.setBc((const uchar*)bc, bclen);
@@ -1546,6 +1682,8 @@ bool UnitRepoProxy::GetUnitStmt
linesBlob(lines);
ue.setLines(lines);
typedefsBlob(ue.m_typedefs);
txn.commit();
} catch (RepoExc& re) {
return true;
@@ -2117,6 +2255,12 @@ PreClassEmitter* UnitEmitter::newPreClassEmitter(const StringData* n,
return pce;
}
Id UnitEmitter::addTypedef(const Typedef& td) {
Id id = m_typedefs.size();
m_typedefs.push_back(td);
return id;
}
void UnitEmitter::recordSourceLocation(const Location* sLoc, Offset start) {
SourceLoc newLoc(*sLoc);
if (!m_sourceLocTab.empty()) {
@@ -2188,7 +2332,8 @@ bool UnitEmitter::insert(UnitOrigin unitOrigin, RepoTxn& txn) {
LineTable lines = createLineTable(m_sourceLocTab, m_bclen);
urp.insertUnit(repoId).insert(txn, m_sn, m_md5, m_bc, m_bclen,
m_bc_meta, m_bc_meta_len,
&m_mainReturn, m_mergeOnly, lines);
&m_mainReturn, m_mergeOnly, lines,
m_typedefs);
}
int64_t usn = m_sn;
for (unsigned i = 0; i < m_litstrs.size(); ++i) {
@@ -2207,6 +2352,7 @@ bool UnitEmitter::insert(UnitOrigin unitOrigin, RepoTxn& txn) {
++it) {
(*it)->commit(txn);
}
for (int i = 0, n = m_mergeableStmts.size(); i < n; i++) {
switch (m_mergeableStmts[i].first) {
case UnitMergeKindDone:
@@ -2302,6 +2448,8 @@ Unit* UnitEmitter::create() {
++it) {
u->m_preClasses.push_back(PreClassPtr((*it)->create(*u)));
}
u->m_typedefs = m_typedefs;
size_t ix = m_fes.size() + m_hoistablePceIdVec.size();
if (m_mergeOnly && !m_allClassesHoistable) {
size_t extra = 0;
+46 -34
Ver Arquivo
@@ -17,13 +17,12 @@
#ifndef incl_VM_UNIT_H_
#define incl_VM_UNIT_H_
#include <tbb/concurrent_unordered_map.h>
// Expects that runtime/vm/core_types.h is already included.
#include "runtime/base/runtime_option.h"
#include "runtime/vm/hhbc.h"
#include "runtime/vm/class.h"
#include "runtime/vm/repo_helpers.h"
#include "runtime/vm/named_entity.h"
#include "runtime/base/array/hphp_array.h"
#include "util/range.h"
#include "util/parser/location.h"
@@ -108,26 +107,10 @@ struct UnitMergeInfo {
}
void*& mergeableObj(int ix) { return ((void**)m_mergeables)[ix]; }
void* mergeableData(int ix) { return (char*)m_mergeables + ix*sizeof(void*); }
};
typedef const uchar* PC;
struct NamedEntity {
Class* m_class;
unsigned m_cachedClassOffset;
unsigned m_cachedFuncOffset;
Class* const* clsList() const { return &m_class; }
void setCachedFunc(Func *f);
Func* getCachedFunc() const;
};
typedef tbb::concurrent_unordered_map<const StringData *, NamedEntity,
string_data_hash,
string_data_isame> NamedEntityMap;
typedef std::pair<const StringData*,const NamedEntity*> NamedEntityPair;
// Exception handler table entry.
class EHEnt {
public:
@@ -276,6 +259,28 @@ struct PreConst {
typedef std::vector<PreConst> PreConstVec;
/*
* This is the runtime representation of a typedef. Typedefs are only
* allowed when hip hop extensions are enabled.
*
* The m_kind field is KindOfObject whenever the typedef is basically
* just a name. At runtime we still might resolve this name to
* another typedef, becoming a typedef for KindOfArray or something in
* that request.
*/
struct Typedef {
const StringData* m_name;
const StringData* m_value;
DataType m_kind;
template<class SerDe> void serde(SerDe& sd) {
sd(m_name)
(m_value)
(m_kind)
;
}
};
//==============================================================================
// (const StringData*) versus (StringData*)
//
@@ -442,7 +447,7 @@ struct Unit {
MD5 md5() const { return m_md5; }
static const NamedEntity* GetNamedEntity(const StringData *);
static NamedEntity* GetNamedEntity(const StringData *);
static Array getUserFunctions();
static Array getClassesInfo();
static Array getInterfacesInfo();
@@ -484,34 +489,39 @@ struct Unit {
static Class* defClass(const HPHP::VM::PreClass* preClass,
bool failIsFatal = true);
void defTypedef(Id id);
static Class *lookupClass(const NamedEntity *ne) {
Class *cls = *ne->clsList(); // TODO(#2054448): ARMv8
if (LIKELY(cls != nullptr)) cls = cls->getCached();
return cls;
Class* cls;
if (LIKELY((cls = ne->getCachedClass()) != nullptr)) {
return cls;
}
return nullptr;
}
static Class *lookupClass(const StringData *clsName) {
return lookupClass(GetNamedEntity(clsName));
}
/*
* Look up a unique class even if it hasn't been loaded in this
* request yet.
*/
static Class *lookupUniqueClass(const NamedEntity *ne) {
Class *cls = *ne->clsList(); // TODO(#2054448): ARMv8
Class* cls = ne->clsList();
if (LIKELY(cls != nullptr)) {
if (cls->attrs() & AttrUnique && RuntimeOption::RepoAuthoritative) {
return cls;
}
cls = cls->getCached();
return cls->getCached();
}
return cls;
return nullptr;
}
static Class *lookupUniqueClass(const StringData *clsName) {
return lookupUniqueClass(GetNamedEntity(clsName));
}
static Class *lookupClass(const StringData *clsName) {
Class *cls = *GetNamedEntity(clsName)->clsList();
if (LIKELY(cls != nullptr)) cls = cls->getCached();
return cls;
}
static Class *loadClass(const NamedEntity *ne,
const StringData *name);
@@ -560,7 +570,6 @@ public:
return m_mergeInfo->hoistableFuncs();
}
void renameFunc(const StringData* oldName, const StringData* newName);
void mergeFuncs() const;
static void loadFunc(const Func *func);
FuncRange funcs() const {
return m_mergeInfo->funcs();
@@ -580,7 +589,6 @@ public:
return m_preClasses[id].get();
}
typedef std::vector<PreClassPtr> PreClassPtrVec;
typedef std::vector<PreClass*> PreClassVec;
typedef Range<PreClassPtrVec> PreClassRange;
void initialMerge();
void merge();
@@ -661,6 +669,7 @@ private:
std::vector<NamedEntityPair> m_namedInfo;
std::vector<const ArrayData*> m_arrays;
PreClassPtrVec m_preClasses;
FixedVector<Typedef> m_typedefs;
UnitMergeInfo* m_mergeInfo;
unsigned m_cacheOffset;
int8_t m_repoId;
@@ -693,6 +702,7 @@ class UnitEmitter {
void setMergeOnly(bool b) { m_mergeOnly = b; }
const MD5& md5() const { return m_md5; }
Id addPreConst(const StringData* name, const TypedValue& value);
Id addTypedef(const Typedef& td);
Id mergeLitstr(const StringData* litstr);
Id mergeArray(ArrayData* a, const StringData* key=nullptr);
FuncEmitter* getMain();
@@ -840,6 +850,7 @@ class UnitEmitter {
std::vector<std::pair<Offset,SourceLoc> > m_sourceLocTab;
std::vector<std::pair<Offset,const FuncEmitter*> > m_feTab;
PreConstVec m_preConsts;
std::vector<Typedef> m_typedefs;
};
class UnitRepoProxy : public RepoProxy {
@@ -876,7 +887,8 @@ class UnitRepoProxy : public RepoProxy {
void insert(RepoTxn& txn, int64_t& unitSn, const MD5& md5, const uchar* bc,
size_t bclen, const uchar* bc_meta, size_t bc_meta_len,
const TypedValue* mainReturn, bool mergeOnly,
const LineTable& lines);
const LineTable& lines,
const std::vector<Typedef>&);
};
class GetUnitStmt : public RepoProxy::Stmt {
public:
+2
Ver Arquivo
@@ -0,0 +1,2 @@
#!/bin/sh
exec sed -e 's@in .*@@g'
+8
Ver Arquivo
@@ -0,0 +1,8 @@
<?hh
function foo(string $y) : shape('a' => string) {
// shapes must have compile-time constant key names
$x = shape($y => 'asd');
}
foo();
+1
Ver Arquivo
@@ -0,0 +1 @@
HipHop Fatal error: syntax error, unexpected T_VARIABLE, expecting ')'
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+9
Ver Arquivo
@@ -0,0 +1,9 @@
<?hh
// Keys must not be numeric or start with integers.
type BadPoint = shape(
'123' => int,
'124' => int,
);
+1
Ver Arquivo
@@ -0,0 +1 @@
HipHop Fatal error: Shape key names may not start with integers
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+100
Ver Arquivo
@@ -0,0 +1,100 @@
<?hh
type Point = shape(
'x' => int,
'y' => int,
);
type Recur = shape(
'shape' => Recur
);
type Point3 = shape(
'x' => int,
'y' => int,
'z' => int,
);
function foo(Point $x) : Point {
$x['x'] = 42;
return $x;
}
function foo2() : shape('x' => int, 'y' => int) {
$x = shape(
'x' => 12,
'y' => 12,
);
return $x;
}
function foo3<T as shape('x' => int)>(T $pt) : T {
$pt['x'] = 42;
return $pt;
}
function foo31<T as shape('x' => int, 'y' => string)>(T $pt) : T {
$pt['x'] = 42;
return $pt;
}
function foo32<T as shape('x' => int, 'y' => string,)>(T $pt) : T {
$pt['x'] = 42;
return $pt;
}
function foo4() {
$x = shape(
'x' => 12,
'y' => 12
);
}
function foo5<T as Point>(T $pt) : T {
$pt['x'] = 42;
return $pt;
}
function foo6<T as shape()>(T $pt) : T {
return $pt;
}
function foo7(shape('id' => int, 'name' => string) $x) : int {
return 12;
}
type Foobar = shape(
'foobar' => string,
);
function foo8(string $foo, string $bar) : Foobar {
$x = shape(
'foobar' => $foo . $bar
);
return 12;
}
// You can have a shape as a parameter to a user attribute.
<<
AttrTakingShape(
shape(
'x' => 1,
'y' => 1,
)
)
>>
class SomeClass {}
// These parse, but don't work at runtime currently:
const Foobar SCALAR_SHAPE = shape(
'foobar' => 'constant'
);
const Point3 SCALAR_SHAPE2 = shape(
'x' => 12,
'y' => 13,
'z' => 11,
);
const SCALAR_SHAPE3 = shape(
'x' => 12,
'foo' => 1.0
);
+3
Ver Arquivo
@@ -0,0 +1,3 @@
HipHop Warning: Constants may only evaluate to scalar values
HipHop Warning: Constants may only evaluate to scalar values
HipHop Warning: Constants may only evaluate to scalar values
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+18
Ver Arquivo
@@ -0,0 +1,18 @@
<?hh
function type() { // ok, 'type' is context sensitive
echo "Hi\n";
}
type();
class Foo {
const TYPE = 'hi2';
}
echo Foo::TYPE . "\n";
type t = int;
function wat(t $type) {
echo $type . "\n";
}
wat(12);
@@ -0,0 +1,3 @@
Hi
hi2
12
+26
Ver Arquivo
@@ -0,0 +1,26 @@
<?hh
class MyClass {
public function __construct() {
echo "ctor\n";
}
public function method() {
echo "method\n";
}
const JUNK = "ASD";
public static $SomeProp = null;
}
type Yeah = MyClass;
function target(Yeah $x): void {
$x->method();
}
function hinting(): void {
$x = new MyClass();
target($x);
}
hinting();
+2
Ver Arquivo
@@ -0,0 +1,2 @@
ctor
method
+10
Ver Arquivo
@@ -0,0 +1,10 @@
<?hh
class MyClass {}
type Foo = MyClass;
function blah(): void {
$x = new Foo(); // Error, new types don't create new value constructors
}
blah();
+1
Ver Arquivo
@@ -0,0 +1 @@
HipHop Fatal error: Class undefined: Foo
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+13
Ver Arquivo
@@ -0,0 +1,13 @@
<?hh
class MyClass {
const ASD = 'wat';
}
type Foo = MyClass;
function test(): void {
// error: making a new type name doesn't make it available outside
// of type expressions (parameter lists, etc).
$x = Foo::ASD;
}
test();
+1
Ver Arquivo
@@ -0,0 +1 @@
HipHop Fatal error: Class undefined: Foo
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+3
Ver Arquivo
@@ -0,0 +1,3 @@
<?hh
type Foo = Foo;
+1
Ver Arquivo
@@ -0,0 +1 @@
HipHop Fatal error: Unknown type or class Foo
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+10
Ver Arquivo
@@ -0,0 +1,10 @@
<?hh
class MyClass {}
class OtherClass {}
type MyType = MyClass;
type MyType = MyClass; //ok
// raise a fatal:
type MyType = OtherClass;
+1
Ver Arquivo
@@ -0,0 +1 @@
HipHop Fatal error: The type MyType is already defined to a different class (MyClass)
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+5
Ver Arquivo
@@ -0,0 +1,5 @@
<?hh
type Coord = shape('x' => int); // ok
type Coord = shape('x' => int,
'y' => int); // ok: typechecker catches it
type Coord = string; // bad
+1
Ver Arquivo
@@ -0,0 +1 @@
HipHop Fatal error: The type Coord is already defined to an incompatible type
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+6
Ver Arquivo
@@ -0,0 +1,6 @@
<?hh
class Existing {
}
type Existing = int; // error, Existing exists
+1
Ver Arquivo
@@ -0,0 +1 @@
HipHop Fatal error: The type Existing is already defined to a different class (Existing)
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+5
Ver Arquivo
@@ -0,0 +1,5 @@
<?hh
type Something = int;
class Something {} // error, Something already exists
+1
Ver Arquivo
@@ -0,0 +1 @@
HipHop Fatal error: The type Something is already defined to a different class (Something)
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+21
Ver Arquivo
@@ -0,0 +1,21 @@
<?hh
type Something = int;
type Something = int;
type Something = int;
type Coord = (int, int);
type Coord = (int, int, int); // only caught by typechecker
type Foo = shape(
'x' => int,
);
type Foo = shape( // only caught by typechecker
'x' => string,
);
class Something2 {}
type SomethingElse = Something2;
type SomethingElse = Something2;
echo "Yeah\n";
+1
Ver Arquivo
@@ -0,0 +1 @@
Yeah
+3
Ver Arquivo
@@ -0,0 +1,3 @@
<?hh
type Yo = IDontExist;
+1
Ver Arquivo
@@ -0,0 +1 @@
HipHop Fatal error: Unknown type or class IDontExist
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+53
Ver Arquivo
@@ -0,0 +1,53 @@
<?hh
newtype Foo = Int;
newtype Foo2 = Integer;
function foo(Foo $x, Foo2 $y) {}
newtype Bar = floAt;
function bar(Bar $k) {}
class bazcls {}
newtype Baz = BazCls;
function baz(Baz $k) {}
function baz2(Bazcls $k) {}
type A = aRray;
type B = iNt;
type C = iNteger;
type D = bOol;
type E = bOolean;
type F = sTring;
type G = rEal;
type H = fLoat;
type I = dOuble;
function lots(A $x,
B $xx,
C $xxx,
D $xxxx,
E $xxxxx,
F $xxxxxx,
G $xxxxxxx,
H $xxxxxxxx,
I $xxxxxxxxx)
{}
function main() {
foo(12, 13);
bar(1.0);
baz(new Bazcls());
baz2(new Bazcls());
lots(array(),
12,
12,
false,
true,
"asd",
1.3,
1.4,
1.5);
}
main();
+5
Ver Arquivo
@@ -0,0 +1,5 @@
<?hh
newtype UserID = int;
function make_user_id(UserId $id): UserID { return $id; }
Ver Arquivo
+37
Ver Arquivo
@@ -0,0 +1,37 @@
<?hh
type MyInt = int;
type MyBool = bool;
type MyDouble = double;
type MyArray = array;
type MyString = string;
function fooInt(MyInt $x): void {
echo "int:\n";
var_dump($x);
}
function fooBool(MyBool $x): void {
echo "bool\n";
var_dump($x);
}
function fooDouble(MyDouble $x): void {
echo "double\n";
var_dump($x);
}
function fooArray(MyArray $x): void {
echo "array:\n";
var_dump($x);
}
function fooString(MyString $x): void {
echo "string:\n";
var_dump($x);
}
function main() {
fooInt(12);
fooBool(false);
fooDouble(1.2);
fooArray(array(1,2,3));
fooString("asdasd");
}
main();
+17
Ver Arquivo
@@ -0,0 +1,17 @@
int:
int(12)
bool
bool(false)
double
float(1.2)
array:
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(3)
}
string:
string(6) "asdasd"
+11
Ver Arquivo
@@ -0,0 +1,11 @@
<?hh
type MyString = string;
function foo(MyString $x): void {
}
function test() {
foo(123); // failure, expected string
}
test();
@@ -0,0 +1 @@
HipHop Fatal error: Argument 1 passed to foo() must be an instance of MyString, int given
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+16
Ver Arquivo
@@ -0,0 +1,16 @@
<?hh
type Point = shape(
'x' => real,
'y' => real,
);
function woot(Point $x) : Point {
return $x;
}
function test(): void {
woot(null); // failure, expected Point
}
test();
@@ -0,0 +1 @@
HipHop Fatal error: Argument 1 passed to woot() must be an instance of Point, null given
+1
Ver Arquivo
@@ -0,0 +1 @@
filter.remove_in
+19
Ver Arquivo
@@ -0,0 +1,19 @@
<?hh
type Point = shape(
'x' => int,
'y' => int,
);
function foo(Point $pt): void {
var_dump($pt);
}
function main(): void {
foo(shape(
'x' => 12,
'y' => 12
));
}
main();
+6
Ver Arquivo
@@ -0,0 +1,6 @@
array(2) {
[0]=>
int(12)
[1]=>
int(12)
}
+10
Ver Arquivo
@@ -0,0 +1,10 @@
<?hh
type Foo = array;
type Bar = Foo;
function main(Bar $x) {
echo "Hi\n";
}
main(array(12));
+1
Ver Arquivo
@@ -0,0 +1 @@
Hi
+1 -2
Ver Arquivo
@@ -7,7 +7,6 @@
# % fbconfig hphp/util/parser/test && fbmake dbg && \
# ./hphp/tools/run_verify_parse.sh
#
: ${FBMAKE_BIN_ROOT=$HPHP_HOME/_bin}
VERIFY_SCRIPT=./test/verify.sh
@@ -18,10 +17,10 @@ PARSE_SKIP='dv_i0.php strict_bad_end.php strict_bad_start.php \
strict_numbers.php syntax-error.php xhp-malformed.php Xhp.php \
trailing_comma_bad1.php trailing_comma_bad2.php trailing_comma_bad3.php \
trailing_comma_bad4.php trailing_comma_bad5.php trailing_comma_bad6.php'
PARSE_SKIP="$PARSE_SKIP $(cd test/vm && ls parse_fail_*.php)"
######################################################################
cd $HPHP_HOME/hphp
skip_list=
for x in $PARSE_SKIP ; do
+5 -2
Ver Arquivo
@@ -189,10 +189,13 @@
YYTOKEN(398, T_TYPELIST_LT),
YYTOKEN(399, T_TYPELIST_GT),
YYTOKEN(400, T_UNRESOLVED_LT),
YYTOKEN(401, T_COLLECTION)
YYTOKEN(401, T_COLLECTION),
YYTOKEN(402, T_SHAPE),
YYTOKEN(403, T_TYPE),
YYTOKEN(404, T_UNRESOLVED_TYPE)
};
#ifndef YYTOKEN_MAX
#define YYTOKEN_MAX 401
#define YYTOKEN_MAX 404
#endif
#endif
+9
Ver Arquivo
@@ -30,6 +30,11 @@
#define SETTOKEN _scanner->setToken(yytext, yyleng)
#define STEPPOS _scanner->stepPos(yytext, yyleng)
#define HH_ONLY_KEYWORD(tok) do { \
SETTOKEN; \
return _scanner->hipHopKeywordsEnabled() ? tok : T_STRING; \
} while (0)
#define IS_LABEL_START(c) \
(((c) >= 'a' && (c) <= 'z') || ((c) >= 'A' && (c) <= 'Z') || \
(c) == '_' || (c) >= 0x7F)
@@ -395,6 +400,10 @@ BACKQUOTE_CHARS ("{"*([^$`\\{]|("\\"{ANY_CHAR}))|{BACKQUOTE_LITERAL_DOLLAR})
<ST_IN_SCRIPTING>"<<" { STEPPOS; return T_SL;}
<ST_IN_SCRIPTING>"..." { SETTOKEN; return T_VARARG; }
<ST_IN_SCRIPTING>"shape" { HH_ONLY_KEYWORD(T_SHAPE); }
<ST_IN_SCRIPTING>"type" { HH_ONLY_KEYWORD(T_UNRESOLVED_TYPE); }
<ST_IN_SCRIPTING>"newtype" { HH_ONLY_KEYWORD(T_UNRESOLVED_TYPE); }
<ST_IN_SCRIPTING>">>" {
if (_scanner->getLookaheadLtDepth() < 2) {
STEPPOS;
+123 -4
Ver Arquivo
@@ -656,6 +656,17 @@ static void only_in_hphp_syntax(Parser *_p) {
}
}
// Shapes may not have leading integers in key names, considered as a
// parse time error. This is because at runtime they are currently
// hphp arrays, which will treat leading integer keys as numbers.
static void validate_shape_keyname(Token& tok, Parser* _p) {
if (tok.text().empty()) {
HPHP_PARSER_ERROR("Shape key names may not be empty", _p);
}
if (isdigit(tok.text()[0])) {
HPHP_PARSER_ERROR("Shape key names may not start with integers", _p);
}
}
///////////////////////////////////////////////////////////////////////////////
@@ -795,6 +806,9 @@ static int yylex(YYSTYPE *token, HPHP::Location *loc, Parser *_p) {
%token T_UNRESOLVED_LT
%token T_COLLECTION
%token T_SHAPE
%token T_TYPE
%token T_UNRESOLVED_TYPE
%%
@@ -814,6 +828,7 @@ top_statement:
| function_declaration_statement { _p->nns(); $$ = $1;}
| class_declaration_statement { _p->nns(); $$ = $1;}
| trait_declaration_statement { _p->nns(); $$ = $1;}
| sm_typedef_statement { $$ = $1; }
| T_HALT_COMPILER '(' ')' ';' { $$.reset();}
| T_NAMESPACE namespace_name ';' { _p->onNamespaceStart($2.text());
$$.reset();}
@@ -1593,8 +1608,9 @@ expr_no_variable:
| T_UNSET_CAST expr { UEXP($$,$2,T_UNSET_CAST,1);}
| T_EXIT exit_expr { UEXP($$,$2,T_EXIT,1);}
| '@' expr { UEXP($$,$2,'@',1);}
| scalar { $$ = $1;}
| array_literal { $$ = $1;}
| scalar { $$ = $1; }
| array_literal { $$ = $1; }
| shape_literal { $$ = $1; }
| '`' backticks_expr '`' { _p->onEncapsList($$,'`',$2);}
| T_PRINT expr { UEXP($$,$2,T_PRINT,1);}
| function_loc
@@ -1618,8 +1634,49 @@ expr_no_variable:
| collection_literal { $$ = $1;}
;
non_empty_shape_pair_list:
non_empty_shape_pair_list ','
T_CONSTANT_ENCAPSED_STRING
T_DOUBLE_ARROW
expr { validate_shape_keyname($3, _p);
_p->onArrayPair($$,&$1,&$3,$5,0); }
| T_CONSTANT_ENCAPSED_STRING
T_DOUBLE_ARROW
expr { validate_shape_keyname($1, _p);
_p->onArrayPair($$, 0,&$1,$3,0); }
;
non_empty_static_shape_pair_list:
non_empty_static_shape_pair_list ','
T_CONSTANT_ENCAPSED_STRING
T_DOUBLE_ARROW
static_scalar { validate_shape_keyname($3, _p);
_p->onArrayPair($$,&$1,&$3,$5,0); }
| T_CONSTANT_ENCAPSED_STRING
T_DOUBLE_ARROW
static_scalar { validate_shape_keyname($1, _p);
_p->onArrayPair($$, 0,&$1,$3,0); }
;
shape_pair_list:
non_empty_shape_pair_list
possible_comma { $$ = $1; }
| { $$.reset(); }
;
static_shape_pair_list:
non_empty_static_shape_pair_list
possible_comma { $$ = $1; }
| { $$.reset(); }
;
shape_literal:
T_SHAPE '(' shape_pair_list ')' { only_in_strict_mode(_p);
_p->onArray($$, $3, T_ARRAY); }
;
array_literal:
T_ARRAY '(' array_pair_list ')' { _p->onArray($$,$3,T_ARRAY);}
T_ARRAY '(' array_pair_list ')' { _p->onArray($$,$3,T_ARRAY);}
;
collection_literal:
@@ -1801,6 +1858,7 @@ xhp_bareword:
| T_NS_C { $$ = $1;}
| T_TRAIT { $$ = $1;}
| T_TRAIT_C { $$ = $1;}
| T_TYPE { $$ = $1;}
;
simple_function_call:
@@ -1867,10 +1925,14 @@ static_scalar:
| '+' static_scalar { UEXP($$,$2,'+',1);}
| '-' static_scalar { UEXP($$,$2,'-',1);}
| T_ARRAY '('
static_array_pair_list ')' { _p->onArray($$,$3,T_ARRAY);}
static_array_pair_list ')' { _p->onArray($$,$3,T_ARRAY); }
| T_SHAPE '('
static_shape_pair_list ')' { only_in_strict_mode(_p);
_p->onArray($$,$3,T_ARRAY); }
| static_class_constant { $$ = $1;}
| static_collection_literal { $$ = $1;}
;
static_class_constant:
class_namespace_string_typeargs
T_PAAMAYIM_NEKUDOTAYIM
@@ -1879,6 +1941,7 @@ static_class_constant:
ident { $1.xhpLabel();
_p->onClassConst($$, $1, $3, 1);}
;
scalar:
namespace_string { _p->onConstantValue($$, $1);}
| T_STRING_VARNAME { _p->onConstantValue($$, $1);}
@@ -1895,6 +1958,7 @@ static_array_pair_list:
possible_comma { $$ = $1;}
| { $$.reset();}
;
possible_comma:
',' { $$.reset();}
| { $$.reset();}
@@ -1903,6 +1967,7 @@ possible_comma_in_hphp_syntax:
',' { only_in_hphp_syntax(_p); $$.reset();}
| { $$.reset();}
;
non_empty_static_array_pair_list:
non_empty_static_array_pair_list
',' static_scalar T_DOUBLE_ARROW
@@ -1937,7 +2002,11 @@ static_scalar_ae:
| '-' static_numeric_scalar_ae { UEXP($$,$2,'-',1);}
| T_ARRAY '('
static_array_pair_list_ae ')' { _p->onArray($$,$3,T_ARRAY);}
| T_SHAPE '('
static_shape_pair_list_ae ')' { only_in_strict_mode(_p);
_p->onArray($$,$3,T_ARRAY); }
;
static_array_pair_list_ae:
non_empty_static_array_pair_list_ae
possible_comma { $$ = $1;}
@@ -1958,6 +2027,23 @@ non_empty_static_scalar_list_ae:
',' static_scalar_ae { _p->onArrayPair($$,&$1, 0,$3,0);}
| static_scalar_ae { _p->onArrayPair($$, 0, 0,$1,0);}
;
static_shape_pair_list_ae:
non_empty_static_shape_pair_list_ae
possible_comma { $$ = $1; }
| { $$.reset(); }
;
non_empty_static_shape_pair_list_ae:
non_empty_static_shape_pair_list_ae
',' T_CONSTANT_ENCAPSED_STRING
T_DOUBLE_ARROW static_scalar_ae { validate_shape_keyname($3, _p);
_p->onArrayPair($$,&$1,&$3,$5,0); }
| T_CONSTANT_ENCAPSED_STRING
T_DOUBLE_ARROW
static_scalar_ae { validate_shape_keyname($1, _p);
_p->onArrayPair($$, 0,&$1,$3,0); }
;
static_scalar_list_ae:
non_empty_static_scalar_list_ae
possible_comma { $$ = $1;}
@@ -1968,6 +2054,7 @@ attribute_static_scalar_list:
| { Token t; t.reset();
_p->onArray($$,t,T_ARRAY);}
;
non_empty_user_attribute_list:
non_empty_user_attribute_list
',' ident
@@ -2240,6 +2327,11 @@ class_constant:
* mode, but simplify down to the original thing
*/
sm_typedef_statement:
T_TYPE ident '=' sm_type ';' { only_in_strict_mode(_p);
_p->onTypedef($$, $2, $4); }
;
sm_name_with_type: /* foo -> int foo */
ident { $$ = $1; }
| sm_type ident { only_in_strict_mode(_p); $$ = $2; }
@@ -2283,10 +2375,36 @@ sm_opt_return_type:
sm_typevar_list:
ident ',' sm_typevar_list { _p->addTypeVar($1.text()); }
| ident { _p->addTypeVar($1.text()); }
| ident T_AS sm_shape_type
sm_typevar_list
| ident T_AS ident ','
sm_typevar_list { _p->addTypeVar($1.text()); }
| ident T_AS ident { _p->addTypeVar($1.text()); }
| ident T_AS sm_shape_type
;
sm_shape_member_type:
T_CONSTANT_ENCAPSED_STRING
T_DOUBLE_ARROW
sm_type { validate_shape_keyname($1, _p); }
;
sm_non_empty_shape_member_list:
sm_non_empty_shape_member_list ','
sm_shape_member_type
| sm_shape_member_type
;
sm_shape_member_list:
sm_non_empty_shape_member_list
possible_comma { $$ = $1; }
| /* empty */
{}
sm_shape_type:
T_SHAPE
'(' sm_shape_member_list ')' { only_in_strict_mode(_p);
$$.setText("array"); }
;
/* extends non_empty_type_decl with some more types */
@@ -2307,6 +2425,7 @@ sm_type:
}
}
| T_ARRAY { $$.setText("array"); }
| sm_shape_type { $$ = $1; }
| T_ARRAY T_TYPELIST_LT sm_type
T_TYPELIST_GT { only_in_strict_mode(_p);
$$.setText("array"); }
+57735 -57366
Ver Arquivo
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff
+86 -15
Ver Arquivo
@@ -226,6 +226,12 @@ void Scanner::nextLookahead(TokenStore::iterator& pos) {
}
}
bool Scanner::nextIfToken(TokenStore::iterator& pos, int tok) {
if (pos->t != tok) return false;
nextLookahead(pos);
return true;
}
bool Scanner::tryParseTypeList(TokenStore::iterator& pos) {
for (;;) {
if (!tryParseNSType(pos)) return false;
@@ -303,6 +309,8 @@ Scanner::tryParseNSType(TokenStore::iterator& pos) {
case T_ARRAY:
nextLookahead(pos);
break;
case T_SHAPE:
return tryParseShapeType(pos);
case T_XHP_LABEL:
nextLookahead(pos);
return true;
@@ -331,11 +339,55 @@ Scanner::tryParseNSType(TokenStore::iterator& pos) {
}
}
bool Scanner::tryParseShapeType(TokenStore::iterator& pos) {
assert(pos->t == T_SHAPE);
nextLookahead(pos);
if (pos->t == T_STRING) {
nextLookahead(pos);
return true;
}
if (pos->t == '(') {
nextLookahead(pos);
if (pos->t != ')') {
if (!tryParseShapeMemberList(pos)) return false;
if (pos->t != ')') return false;
}
nextLookahead(pos);
return true;
}
return false;
}
bool Scanner::tryParseShapeMemberList(TokenStore::iterator& pos) {
assert(pos->t != ')'); // already determined to be nonempty
for (;;) {
if (!nextIfToken(pos, T_CONSTANT_ENCAPSED_STRING) ||
!nextIfToken(pos, T_DOUBLE_ARROW)) {
return false;
}
if (!tryParseNSType(pos)) return false;
if (pos->t == ')') return true;
if (!nextIfToken(pos, ',')) return false;
if (pos->t == ')') return true;
}
return false;
}
static bool isUnresolved(int tokid) {
return tokid == T_UNRESOLVED_LT ||
tokid == T_UNRESOLVED_TYPE;
}
int Scanner::getNextToken(ScannerToken &t, Location &l) {
int tokid;
bool la = !m_lookahead.empty();
tokid = fetchToken(t, l);
if (LIKELY(tokid != T_UNRESOLVED_LT)) {
if (LIKELY(!isUnresolved(tokid))) {
// In the common case, we don't have to perform any resolution
// and we can just return the token
if (UNLIKELY(la)) {
@@ -345,7 +397,7 @@ int Scanner::getNextToken(ScannerToken &t, Location &l) {
}
return tokid;
}
// We encountered a '<' character that needs to be resolved.
if (!la) {
// If this token didn't come from the lookahead store, we
// need to stash it there
@@ -353,20 +405,39 @@ int Scanner::getNextToken(ScannerToken &t, Location &l) {
LookaheadToken ltd = { t, l, tokid };
*it = ltd;
}
// Look at subsequent tokens to determine if the '<' character
// is the start of a type list
TokenStore::iterator pos = m_lookahead.begin();
TokenStore::iterator ltPos = pos;
nextLookahead(pos);
++m_lookaheadLtDepth;
bool isTypeList = tryParseTypeList(pos);
--m_lookaheadLtDepth;
if (!isTypeList || pos->t != '>') {
ltPos->t = '<';
} else {
ltPos->t = T_TYPELIST_LT;
pos->t = T_TYPELIST_GT;
switch (tokid) {
case T_UNRESOLVED_TYPE: {
auto pos = m_lookahead.begin();
auto typePos = pos;
nextLookahead(pos);
if (pos->t == T_STRING) {
typePos->t = T_TYPE;
} else {
typePos->t = T_STRING;
}
break;
}
case T_UNRESOLVED_LT: {
// Look at subsequent tokens to determine if the '<' character
// is the start of a type list
auto pos = m_lookahead.begin();
auto ltPos = pos;
nextLookahead(pos);
++m_lookaheadLtDepth;
bool isTypeList = tryParseTypeList(pos);
--m_lookaheadLtDepth;
if (!isTypeList || pos->t != '>') {
ltPos->t = '<';
} else {
ltPos->t = T_TYPELIST_LT;
pos->t = T_TYPELIST_GT;
}
break;
}
default: always_assert(0);
}
tokid = fetchToken(t, l);
// We pulled a lookahead token, we need to remove it from the
// lookahead store
+17 -4
Ver Arquivo
@@ -168,9 +168,10 @@ struct TokenStore {
class Scanner {
public:
enum Type {
AllowShortTags = 1, // allow <?
AllowAspTags = 2, // allow <% %>
ReturnAllTokens = 8, // return comments and whitespaces
AllowShortTags = 0x01, // allow <?
AllowAspTags = 0x02, // allow <% %>
ReturnAllTokens = 0x08, // return comments and whitespaces
EnableHipHopKeywords = 0x10, // allow hip-hop specific reserved words
};
public:
@@ -277,15 +278,27 @@ public:
m_isStrictMode = 1;
}
bool isStrictMode() {
bool isStrictMode() const {
return m_isStrictMode;
}
/*
* Returns: whether HipHop-extension keywords are enabled.
*/
bool hipHopKeywordsEnabled() const {
return m_type & EnableHipHopKeywords;
}
int getLookaheadLtDepth() {
return m_lookaheadLtDepth;
}
private:
bool tryParseShapeType(TokenStore::iterator& pos);
bool tryParseShapeMemberList(TokenStore::iterator& pos);
bool nextIfToken(TokenStore::iterator& pos, int tok);
void computeMd5();
std::string m_filename;
Diferenças do arquivo suprimidas por serem muito extensas Carregar Diff

Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais