Implement most bitwise arithmetic outside of Variant
Everything except unary bitwise not.
Esse commit está contido em:
@@ -524,31 +524,44 @@ ExpressionPtr BinaryOpExpression::foldConst(AnalysisResultConstPtr ar) {
|
||||
Variant result;
|
||||
switch (m_op) {
|
||||
case T_LOGICAL_XOR:
|
||||
result = static_cast<bool>(v1.toBoolean() ^ v2.toBoolean()); break;
|
||||
result = static_cast<bool>(v1.toBoolean() ^ v2.toBoolean());
|
||||
break;
|
||||
case '|':
|
||||
result = bitwise_or(v1, v2); break;
|
||||
*result.asCell() = cellBitOr(*v1.asCell(), *v2.asCell());
|
||||
break;
|
||||
case '&':
|
||||
result = bitwise_and(v1, v2); break;
|
||||
*result.asCell() = cellBitAnd(*v1.asCell(), *v2.asCell());
|
||||
break;
|
||||
case '^':
|
||||
result = bitwise_xor(v1, v2); break;
|
||||
*result.asCell() = cellBitXor(*v1.asCell(), *v2.asCell());
|
||||
break;
|
||||
case '.':
|
||||
result = concat(v1.toString(), v2.toString()); break;
|
||||
result = concat(v1.toString(), v2.toString());
|
||||
break;
|
||||
case T_IS_IDENTICAL:
|
||||
result = same(v1, v2); break;
|
||||
result = same(v1, v2);
|
||||
break;
|
||||
case T_IS_NOT_IDENTICAL:
|
||||
result = !same(v1, v2); break;
|
||||
result = !same(v1, v2);
|
||||
break;
|
||||
case T_IS_EQUAL:
|
||||
result = equal(v1, v2); break;
|
||||
result = equal(v1, v2);
|
||||
break;
|
||||
case T_IS_NOT_EQUAL:
|
||||
result = !equal(v1, v2); break;
|
||||
result = !equal(v1, v2);
|
||||
break;
|
||||
case '<':
|
||||
result = less(v1, v2); break;
|
||||
result = less(v1, v2);
|
||||
break;
|
||||
case T_IS_SMALLER_OR_EQUAL:
|
||||
result = cellLessOrEqual(*v1.asCell(), *v2.asCell()); break;
|
||||
result = cellLessOrEqual(*v1.asCell(), *v2.asCell());
|
||||
break;
|
||||
case '>':
|
||||
result = more(v1, v2); break;
|
||||
result = more(v1, v2);
|
||||
break;
|
||||
case T_IS_GREATER_OR_EQUAL:
|
||||
result = cellGreaterOrEqual(*v1.asCell(), *v2.asCell()); break;
|
||||
result = cellGreaterOrEqual(*v1.asCell(), *v2.asCell());
|
||||
break;
|
||||
case '+':
|
||||
*result.asCell() = cellAdd(*v1.asCell(), *v2.asCell());
|
||||
break;
|
||||
|
||||
@@ -90,9 +90,6 @@ bool empty(CVarRef v, CStrRef, bool = false) = delete;
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// operators
|
||||
|
||||
inline Variant bitwise_or (CVarRef v1, CVarRef v2) { return v1 | v2;}
|
||||
inline Variant bitwise_and(CVarRef v1, CVarRef v2) { return v1 & v2;}
|
||||
inline Variant bitwise_xor(CVarRef v1, CVarRef v2) { return v1 ^ v2;}
|
||||
inline int64_t shift_left(int64_t v1, int64_t v2) { return v1 << v2; }
|
||||
inline int64_t shift_right(int64_t v1, int64_t v2) { return v1 >> v2; }
|
||||
|
||||
|
||||
@@ -230,10 +230,12 @@ inline ALWAYS_INLINE unsigned typeToDestrIndex(DataType t) {
|
||||
// Helper macro for checking if a given type is refcounted
|
||||
#define IS_REFCOUNTED_TYPE(t) ((t) > KindOfRefCountThreshold)
|
||||
|
||||
// Helper macro for checking if a type is KindOfString or KindOfStaticString.
|
||||
// Helper function for checking if a type is KindOfString or KindOfStaticString.
|
||||
static_assert(KindOfStaticString == 0x0C, "");
|
||||
static_assert(KindOfString == 0x14, "");
|
||||
#define IS_STRING_TYPE(t) (((t) & ~0x18) == KindOfStringBit)
|
||||
inline bool IS_STRING_TYPE(DataType t) {
|
||||
return (t & ~0x18) == KindOfStringBit;
|
||||
}
|
||||
|
||||
// Check if a type is KindOfUninit or KindOfNull
|
||||
#define IS_NULL_TYPE(t) (unsigned(t) <= KindOfNull)
|
||||
|
||||
@@ -234,15 +234,13 @@ public:
|
||||
Util::s_stackSize));
|
||||
}
|
||||
|
||||
/**
|
||||
* Informational.
|
||||
*/
|
||||
const char *data() const {
|
||||
// TODO: t1800106: re-enable this assert
|
||||
//assert(rawdata()[size()] == 0); // all strings must be null-terminated
|
||||
return rawdata();
|
||||
}
|
||||
// This method should only be used internally by the String class.
|
||||
char* mutableData() const { return m_data; }
|
||||
|
||||
int size() const { return m_len; }
|
||||
static uint sizeOffset() { return offsetof(StringData, m_len); }
|
||||
int capacity() const { return isSmall() ? MaxSmallSize : bigCap(); }
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <type_traits>
|
||||
#include <limits>
|
||||
#include <algorithm>
|
||||
|
||||
#include "hphp/runtime/base/runtime_error.h"
|
||||
#include "hphp/runtime/base/tv_conversions.h"
|
||||
@@ -237,6 +238,51 @@ struct MulEq {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<class SzOp, class BitOp>
|
||||
StringData* stringBitOp(BitOp bop, SzOp sop, StringData* s1, StringData* s2) {
|
||||
auto const newLen = sop(s1->size(), s2->size());
|
||||
auto const newStr = NEW(StringData)(newLen);
|
||||
auto const s1Data = s1->data();
|
||||
auto const s2Data = s2->data();
|
||||
auto const outData = newStr->mutableData();
|
||||
|
||||
for (uint32_t i = 0; i < newLen; ++i) {
|
||||
outData[i] = bop(s1Data[i], s2Data[i]);
|
||||
}
|
||||
newStr->setSize(newLen);
|
||||
|
||||
newStr->setRefCount(1);
|
||||
return newStr;
|
||||
}
|
||||
|
||||
template<template<class> class BitOp, class StrLenOp>
|
||||
Cell cellBitOp(StrLenOp strLenOp, Cell c1, Cell c2) {
|
||||
assert(cellIsPlausible(&c1));
|
||||
assert(cellIsPlausible(&c2));
|
||||
|
||||
if (IS_STRING_TYPE(c1.m_type) && IS_STRING_TYPE(c2.m_type)) {
|
||||
return make_tv<KindOfString>(
|
||||
stringBitOp(
|
||||
BitOp<char>(),
|
||||
strLenOp,
|
||||
c1.m_data.pstr,
|
||||
c2.m_data.pstr
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return make_tv<KindOfInt64>(
|
||||
BitOp<int64_t>()(cellToInt(c1), cellToInt(c2))
|
||||
);
|
||||
}
|
||||
|
||||
template<class Op>
|
||||
void cellBitOpEq(Op op, Cell& c1, Cell c2) {
|
||||
auto const result = op(c1, c2);
|
||||
cellSet(result, c1);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@@ -271,6 +317,27 @@ Cell cellMod(Cell c1, Cell c2) {
|
||||
return make_tv<KindOfInt64>(i1 % i2);
|
||||
}
|
||||
|
||||
Cell cellBitAnd(Cell c1, Cell c2) {
|
||||
return cellBitOp<std::bit_and>(
|
||||
[] (uint32_t a, uint32_t b) { return std::min(a, b); },
|
||||
c1, c2
|
||||
);
|
||||
}
|
||||
|
||||
Cell cellBitOr(Cell c1, Cell c2) {
|
||||
return cellBitOp<std::bit_or>(
|
||||
[] (uint32_t a, uint32_t b) { return std::max(a, b); },
|
||||
c1, c2
|
||||
);
|
||||
}
|
||||
|
||||
Cell cellBitXor(Cell c1, Cell c2) {
|
||||
return cellBitOp<std::bit_xor>(
|
||||
[] (uint32_t a, uint32_t b) { return std::min(a, b); },
|
||||
c1, c2
|
||||
);
|
||||
}
|
||||
|
||||
void cellAddEq(Cell& c1, Cell c2) {
|
||||
cellOpEq(AddEq(), c1, c2);
|
||||
}
|
||||
@@ -296,6 +363,18 @@ void cellModEq(Cell& c1, Cell c2) {
|
||||
cellCopy(cellMod(c1, c2), c1);
|
||||
}
|
||||
|
||||
void cellBitAndEq(Cell& c1, Cell c2) {
|
||||
cellBitOpEq(cellBitAnd, c1, c2);
|
||||
}
|
||||
|
||||
void cellBitOrEq(Cell& c1, Cell c2) {
|
||||
cellBitOpEq(cellBitOr, c1, c2);
|
||||
}
|
||||
|
||||
void cellBitXorEq(Cell& c1, Cell c2) {
|
||||
cellBitOpEq(cellBitXor, c1, c2);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
|
||||
@@ -53,6 +53,16 @@ TypedNum cellMul(Cell, Cell);
|
||||
Cell cellDiv(Cell, Cell);
|
||||
Cell cellMod(Cell, Cell);
|
||||
|
||||
/*
|
||||
* PHP operators &, |, and ^.
|
||||
*
|
||||
* These functions return a KindOfInt64, unless both arguments are
|
||||
* KindOfString, in which case they return a KindOfString.
|
||||
*/
|
||||
Cell cellBitAnd(Cell, Cell);
|
||||
Cell cellBitOr(Cell, Cell);
|
||||
Cell cellBitXor(Cell, Cell);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
/*
|
||||
@@ -89,6 +99,18 @@ void cellMulEq(Cell& c1, Cell);
|
||||
void cellDivEq(Cell& c1, Cell);
|
||||
void cellModEq(Cell& c1, Cell);
|
||||
|
||||
/*
|
||||
* PHP operators &=, |=, and ^=.
|
||||
*
|
||||
* Mutates the first argument in place, by combining the second
|
||||
* argument with it in the sense of the appropriate operator.
|
||||
*
|
||||
* Post: c1.m_type == KindOfString || c1.m_type == KindOfInt64
|
||||
*/
|
||||
void cellBitAndEq(Cell& c1, Cell);
|
||||
void cellBitOrEq(Cell& c1, Cell);
|
||||
void cellBitXorEq(Cell& c1, Cell);
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
|
||||
@@ -401,84 +401,6 @@ String String::operator~() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
String String::operator|(CStrRef v) const {
|
||||
return String(m_px).operator|=(v);
|
||||
}
|
||||
|
||||
String String::operator&(CStrRef v) const {
|
||||
return String(m_px).operator&=(v);
|
||||
}
|
||||
|
||||
String String::operator^(CStrRef v) const {
|
||||
return String(m_px).operator^=(v);
|
||||
}
|
||||
|
||||
String &String::operator|=(CStrRef v) {
|
||||
const char *s1 = data();
|
||||
const char *s2 = v.data();
|
||||
int len1 = size();
|
||||
int len2 = v.size();
|
||||
int len;
|
||||
char *copy = nullptr;
|
||||
if (len2 > len1) {
|
||||
len = len2;
|
||||
copy = string_duplicate(s2, len2);
|
||||
for (int i = 0; i < len1; i++) copy[i] |= s1[i];
|
||||
} else {
|
||||
len = len1;
|
||||
copy = string_duplicate(s1, len1);
|
||||
for (int i = 0; i < len2; i++) copy[i] |= s2[i];
|
||||
}
|
||||
if (m_px) decRefStr(m_px);
|
||||
m_px = NEW(StringData)(copy, len, AttachString);
|
||||
m_px->setRefCount(1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
String &String::operator&=(CStrRef v) {
|
||||
const char *s1 = data();
|
||||
const char *s2 = v.data();
|
||||
int len1 = size();
|
||||
int len2 = v.size();
|
||||
int len;
|
||||
char *copy = nullptr;
|
||||
if (len2 < len1) {
|
||||
len = len2;
|
||||
copy = string_duplicate(s2, len2);
|
||||
for (int i = 0; i < len2; i++) copy[i] &= s1[i];
|
||||
} else {
|
||||
len = len1;
|
||||
copy = string_duplicate(s1, len1);
|
||||
for (int i = 0; i < len1; i++) copy[i] &= s2[i];
|
||||
}
|
||||
if (m_px) decRefStr(m_px);
|
||||
m_px = NEW(StringData)(copy, len, AttachString);
|
||||
m_px->setRefCount(1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
String &String::operator^=(CStrRef v) {
|
||||
const char *s1 = data();
|
||||
const char *s2 = v.data();
|
||||
int len1 = size();
|
||||
int len2 = v.size();
|
||||
int len;
|
||||
char *copy = nullptr;
|
||||
if (len2 < len1) {
|
||||
len = len2;
|
||||
copy = string_duplicate(s2, len2);
|
||||
for (int i = 0; i < len2; i++) copy[i] ^= s1[i];
|
||||
} else {
|
||||
len = len1;
|
||||
copy = string_duplicate(s1, len1);
|
||||
for (int i = 0; i < len1; i++) copy[i] ^= s2[i];
|
||||
}
|
||||
if (m_px) decRefStr(m_px);
|
||||
m_px = NEW(StringData)(copy, len, AttachString);
|
||||
m_px->setRefCount(1);
|
||||
return *this;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// conversions
|
||||
|
||||
|
||||
@@ -311,12 +311,12 @@ public:
|
||||
String &operator += (CStrRef v);
|
||||
String &operator += (const StringSlice& slice);
|
||||
String &operator += (const MutableSlice& slice);
|
||||
String operator | (CStrRef v) const;
|
||||
String operator & (CStrRef v) const;
|
||||
String operator ^ (CStrRef v) const;
|
||||
String &operator |= (CStrRef v);
|
||||
String &operator &= (CStrRef v);
|
||||
String &operator ^= (CStrRef v);
|
||||
String operator | (CStrRef v) const = delete;
|
||||
String operator & (CStrRef v) const = delete;
|
||||
String operator ^ (CStrRef v) const = delete;
|
||||
String &operator |= (CStrRef v) = delete;
|
||||
String &operator &= (CStrRef v) = delete;
|
||||
String &operator ^= (CStrRef v) = delete;
|
||||
String operator ~ () const;
|
||||
explicit operator std::string () const {
|
||||
return std::string(c_str(), size());
|
||||
|
||||
@@ -624,54 +624,6 @@ Variant Variant::bitNot() const {
|
||||
throw InvalidOperandException("only numerics and strings can be negated");
|
||||
}
|
||||
|
||||
Variant Variant::operator|(CVarRef v) const {
|
||||
if (isString() && v.isString()) {
|
||||
return toString() | v.toString();
|
||||
}
|
||||
return toInt64() | v.toInt64();
|
||||
}
|
||||
|
||||
Variant Variant::operator&(CVarRef v) const {
|
||||
if (isString() && v.isString()) {
|
||||
return toString() & v.toString();
|
||||
}
|
||||
return toInt64() & v.toInt64();
|
||||
}
|
||||
|
||||
Variant Variant::operator^(CVarRef v) const {
|
||||
if (isString() && v.isString()) {
|
||||
return toString() ^ v.toString();
|
||||
}
|
||||
return toInt64() ^ v.toInt64();
|
||||
}
|
||||
|
||||
Variant &Variant::operator|=(CVarRef v) {
|
||||
if (isString() && v.isString()) {
|
||||
set(toString() | v.toString());
|
||||
} else {
|
||||
set(toInt64() | v.toInt64());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Variant &Variant::operator&=(CVarRef v) {
|
||||
if (isString() && v.isString()) {
|
||||
set(toString() & v.toString());
|
||||
} else {
|
||||
set(toInt64() & v.toInt64());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Variant &Variant::operator^=(CVarRef v) {
|
||||
if (isString() && v.isString()) {
|
||||
set(toString() ^ v.toString());
|
||||
} else {
|
||||
set(toInt64() ^ v.toInt64());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
Variant &Variant::operator<<=(int64_t n) {
|
||||
set(toInt64() << n);
|
||||
return *this;
|
||||
|
||||
@@ -575,12 +575,12 @@ class Variant : private TypedValue {
|
||||
Variant &operator %= (int64_t n) = delete;
|
||||
Variant &operator %= (double n) = delete;
|
||||
|
||||
Variant operator | (CVarRef v) const;
|
||||
Variant &operator |= (CVarRef v);
|
||||
Variant operator & (CVarRef v) const;
|
||||
Variant &operator &= (CVarRef v);
|
||||
Variant operator ^ (CVarRef v) const;
|
||||
Variant &operator ^= (CVarRef v);
|
||||
Variant operator | (CVarRef v) const = delete;
|
||||
Variant &operator |= (CVarRef v) = delete;
|
||||
Variant operator & (CVarRef v) const = delete;
|
||||
Variant &operator &= (CVarRef v) = delete;
|
||||
Variant operator ^ (CVarRef v) const = delete;
|
||||
Variant &operator ^= (CVarRef v) = delete;
|
||||
Variant &operator <<=(int64_t n);
|
||||
Variant &operator >>=(int64_t n);
|
||||
|
||||
|
||||
@@ -3708,6 +3708,18 @@ inline void OPTBLD_INLINE VMExecutionContext::iopMod(PC& pc) {
|
||||
implCellBinOp(pc, cellMod);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopBitAnd(PC& pc) {
|
||||
implCellBinOp(pc, cellBitAnd);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopBitOr(PC& pc) {
|
||||
implCellBinOp(pc, cellBitOr);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopBitXor(PC& pc) {
|
||||
implCellBinOp(pc, cellBitXor);
|
||||
}
|
||||
|
||||
template<class Op>
|
||||
void OPTBLD_INLINE VMExecutionContext::implCellBinOpBool(PC& pc, Op op) {
|
||||
NEXT();
|
||||
@@ -3767,41 +3779,6 @@ inline void OPTBLD_INLINE VMExecutionContext::iopGte(PC& pc) {
|
||||
implCellBinOpBool(pc, cellGreaterOrEqual);
|
||||
}
|
||||
|
||||
#define MATHOP(OP, VOP) do { \
|
||||
NEXT(); \
|
||||
Cell* c1 = m_stack.topC(); \
|
||||
Cell* c2 = m_stack.indC(1); \
|
||||
if (c2->m_type == KindOfInt64 && c1->m_type == KindOfInt64) { \
|
||||
int64_t a = c2->m_data.num; \
|
||||
int64_t b = c1->m_data.num; \
|
||||
MATHOP_DIVCHECK(0) \
|
||||
c2->m_data.num = a OP b; \
|
||||
m_stack.popX(); \
|
||||
} \
|
||||
MATHOP_DOUBLE(OP) \
|
||||
else { \
|
||||
tvCellAsVariant(c2) = VOP(tvCellAsVariant(c2), tvCellAsCVarRef(c1)); \
|
||||
m_stack.popC(); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define MATHOP_DOUBLE(OP)
|
||||
#define MATHOP_DIVCHECK(x)
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopBitAnd(PC& pc) {
|
||||
MATHOP(&, bitwise_and);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopBitOr(PC& pc) {
|
||||
MATHOP(|, bitwise_or);
|
||||
}
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopBitXor(PC& pc) {
|
||||
MATHOP(^, bitwise_xor);
|
||||
}
|
||||
#undef MATHOP
|
||||
#undef MATHOP_DOUBLE
|
||||
#undef MATHOP_DIVCHECK
|
||||
|
||||
inline void OPTBLD_INLINE VMExecutionContext::iopBitNot(PC& pc) {
|
||||
NEXT();
|
||||
Cell* c1 = m_stack.topC();
|
||||
|
||||
@@ -47,9 +47,9 @@ void SETOP_BODY(TypedValue* lhs, unsigned char op, Cell* rhs) {
|
||||
case SetOpConcatEqual:
|
||||
concat_assign(tvAsVariant(lhs), tvCellAsCVarRef(rhs).toString());
|
||||
break;
|
||||
case SetOpAndEqual: tvAsVariant(lhs) &= tvCellAsCVarRef(rhs); break;
|
||||
case SetOpOrEqual: tvAsVariant(lhs) |= tvCellAsCVarRef(rhs); break;
|
||||
case SetOpXorEqual: tvAsVariant(lhs) ^= tvCellAsCVarRef(rhs); break;
|
||||
case SetOpAndEqual: cellBitAndEq(*lhs, *rhs); break;
|
||||
case SetOpOrEqual: cellBitOrEq(*lhs, *rhs); break;
|
||||
case SetOpXorEqual: cellBitXorEq(*lhs, *rhs); break;
|
||||
case SetOpSlEqual: tvAsVariant(lhs) <<=
|
||||
tvCellAsCVarRef(rhs).toInt64(); break;
|
||||
case SetOpSrEqual: tvAsVariant(lhs) >>=
|
||||
|
||||
@@ -168,14 +168,6 @@ bool TestCppBase::TestString() {
|
||||
s = String("c") + String("d"); VS(s.c_str(), "cd");
|
||||
s += "efg"; VS(s.c_str(), "cdefg");
|
||||
s += String("hij"); VS(s.c_str(), "cdefghij");
|
||||
|
||||
s = String("\x50\x51") | "\x51\x51"; VS(s.c_str(), "\x51\x51");
|
||||
s = String("\x50\x51") & "\x51\x51"; VS(s.c_str(), "\x50\x51");
|
||||
s = String("\x50\x51") ^ "\x51\x51"; VS(s.c_str(), "\x01");
|
||||
s = "\x50\x51"; s |= "\x51\x51"; VS(s.c_str(), "\x51\x51");
|
||||
s = "\x50\x51"; s &= "\x51\x51"; VS(s.c_str(), "\x50\x51");
|
||||
s = "\x50\x51"; s ^= "\x51\x51"; VS(s.c_str(), "\x01");
|
||||
s = "\x50\x51"; s = ~s; VS(s.c_str(), "\xAF\xAE");
|
||||
}
|
||||
|
||||
// manipulations
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
function VS($a, $b) {
|
||||
if ($a === $b) var_dump("ok");
|
||||
else {
|
||||
var_dump($a);
|
||||
var_dump(debug_backtrace());
|
||||
}
|
||||
}
|
||||
|
||||
$s = "\x50\x51" | "\x51\x51"; VS($s, "\x51\x51");
|
||||
$s = "\x50\x51" & "\x51\x51"; VS($s, "\x50\x51");
|
||||
$s = "\x50\x51" ^ "\x51\x51"; VS($s, "\x01\x00");
|
||||
$s = "\x50\x51"; $s |= "\x51\x51"; VS($s, "\x51\x51");
|
||||
$s = "\x50\x51"; $s &= "\x51\x51"; VS($s, "\x50\x51");
|
||||
$s = "\x50\x51"; $s ^= "\x51\x51"; VS($s, "\x01\x00");
|
||||
$s = "\x50\x51"; $s = ~$s; VS($s, "\xAF\xAE");
|
||||
@@ -0,0 +1,7 @@
|
||||
string(2) "ok"
|
||||
string(2) "ok"
|
||||
string(2) "ok"
|
||||
string(2) "ok"
|
||||
string(2) "ok"
|
||||
string(2) "ok"
|
||||
string(2) "ok"
|
||||
Referência em uma Nova Issue
Bloquear um usuário