Commit 925c13d5 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Merge pull request #559 from kmod/threading_local4

switch to CPython's thread._local implementation
parents 73140839 d48961f6
......@@ -64,6 +64,7 @@ file(GLOB_RECURSE STDMODULE_SRCS Modules
stringio.c
stropmodule.c
textio.c
threadmodule.c
timemodule.c
unicodedata.c
util.c
......
......@@ -14,8 +14,8 @@ extern "C" {
// Pyston change: this is no longer a static object
//PyAPI_DATA(PyTypeObject) PyCFunction_Type;
PyAPI_DATA(PyTypeObject*) builtin_function_or_method_cls;
#define PyCFunction_Type (*builtin_function_or_method_cls)
PyAPI_DATA(PyTypeObject*) capifunc_cls;
#define PyCFunction_Type (*capifunc_cls)
#define PyCFunction_Check(op) (Py_TYPE(op) == &PyCFunction_Type)
......@@ -28,6 +28,8 @@ PyAPI_FUNC(PyCFunction) PyCFunction_GetFunction(PyObject *) PYSTON_NOEXCEPT;
PyAPI_FUNC(PyObject *) PyCFunction_GetSelf(PyObject *) PYSTON_NOEXCEPT;
PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *) PYSTON_NOEXCEPT;
// Pyston change: changed to function calls
#if 0
/* Macros for direct access to these values. Type checks are *not*
done, so use with care. */
#define PyCFunction_GET_FUNCTION(func) \
......@@ -36,6 +38,13 @@ PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *) PYSTON_NOEXCEPT;
(((PyCFunctionObject *)func) -> m_self)
#define PyCFunction_GET_FLAGS(func) \
(((PyCFunctionObject *)func) -> m_ml -> ml_flags)
#endif
#define PyCFunction_GET_FUNCTION(func) \
PyCFunction_GetFunction((PyObject*)(func))
#define PyCFunction_GET_SELF(func) \
PyCFunction_GetSelf((PyObject*)(func))
#define PyCFunction_GET_FLAGS(func) \
PyCFunction_GetFlags((PyObject*)(func))
PyAPI_FUNC(PyObject *) PyCFunction_Call(PyObject *, PyObject *, PyObject *) PYSTON_NOEXCEPT;
struct PyMethodDef {
......
......@@ -244,14 +244,14 @@
#define HAVE_SYS_WAIT_H 1
#define HAVE_TCGETPGRP 1
#define HAVE_TCSETPGRP 1
#define HAVE_TEMPNAM 1
//#define HAVE_TEMPNAM 1 // pyston change: we have them but I dislike the compiler warnings they generate
#define HAVE_TERM_H 1
#define HAVE_TERMIOS_H 1
#define HAVE_TGAMMA 1
#define HAVE_TIMEGM 1
#define HAVE_TIMES 1
#define HAVE_TMPFILE 1
#define HAVE_TMPNAM 1
//#define HAVE_TMPNAM 1 // pyston change: we have them but I dislike the compiler warnings they generate
#define HAVE_TMPNAM_R 1
#define HAVE_TM_ZONE 1
#define HAVE_TRUNCATE 1
......
......@@ -13,8 +13,10 @@
#include "pythread.h"
static PyObject *ThreadError;
// Pyston change: we're only using part of this file
static PyObject *str_dict;
#if 0
static PyObject *ThreadError;
static long nb_threads = 0;
/* Lock objects */
......@@ -173,6 +175,7 @@ newlockobject(void)
}
return self;
}
#endif
/* Thread-local objects */
......@@ -236,7 +239,7 @@ static PyTypeObject localdummytype = {
/* tp_name */ "_thread._localdummy",
/* tp_basicsize */ sizeof(localdummyobject),
/* tp_itemsize */ 0,
/* tp_dealloc */ (destructor)localdummy_dealloc,
/* tp_dealloc */ (destructor)/*localdummy_dealloc*/ NULL,
/* tp_print */ 0,
/* tp_getattr */ 0,
/* tp_setattr */ 0,
......@@ -389,6 +392,10 @@ local_traverse(localobject *self, visitproc visit, void *arg)
static int
local_clear(localobject *self)
{
// Pyston change:
Py_FatalError("unexpected call to local_clear()");
abort();
#if 0
PyThreadState *tstate;
Py_CLEAR(self->args);
Py_CLEAR(self->kw);
......@@ -406,6 +413,7 @@ local_clear(localobject *self)
PyDict_DelItem(tstate->dict, self->key);
}
return 0;
#endif
}
static void
......@@ -490,7 +498,7 @@ static PyTypeObject localtype = {
/* tp_name */ "thread._local",
/* tp_basicsize */ sizeof(localobject),
/* tp_itemsize */ 0,
/* tp_dealloc */ (destructor)local_dealloc,
/* tp_dealloc */ (destructor)/*local_dealloc*/ NULL,
/* tp_print */ 0,
/* tp_getattr */ 0,
/* tp_setattr */ 0,
......@@ -589,6 +597,8 @@ _localdummy_destroyed(PyObject *localweakref, PyObject *dummyweakref)
Py_RETURN_NONE;
}
// Pyston change:
#if 0
/* Module functions */
struct bootstate {
......@@ -846,8 +856,11 @@ requiring allocation in multiples of the system memory page size\n\
- platform documentation should be referred to for more information\n\
(4kB pages are common; using multiples of 4096 for the stack size is\n\
the suggested approach in the absence of more specific information).");
#endif
static PyMethodDef thread_methods[] = {
// Pyston change:
#if 0
{"start_new_thread", (PyCFunction)thread_PyThread_start_new_thread,
METH_VARARGS,
start_new_doc},
......@@ -871,6 +884,7 @@ static PyMethodDef thread_methods[] = {
{"stack_size", (PyCFunction)thread_stack_size,
METH_VARARGS,
stack_size_doc},
#endif
{NULL, NULL} /* sentinel */
};
......@@ -909,6 +923,8 @@ initthread(void)
if (m == NULL)
return;
// Pyston change:
#if 0
/* Add a symbolic constant */
d = PyModule_GetDict(m);
ThreadError = PyGC_AddRoot(PyErr_NewException("thread.error", NULL, NULL));
......@@ -918,17 +934,18 @@ initthread(void)
return;
Py_INCREF(&Locktype);
PyDict_SetItemString(d, "LockType", (PyObject *)&Locktype);
#endif
Py_INCREF(&localtype);
if (PyModule_AddObject(m, "_local", (PyObject *)&localtype) < 0)
return;
nb_threads = 0;
//nb_threads = 0; // pyston change
str_dict = PyGC_AddRoot(PyString_InternFromString("__dict__"));
if (str_dict == NULL)
return;
/* Initialize the C thread library */
PyThread_init_thread();
//PyThread_init_thread(); // pyston change
}
......@@ -425,8 +425,7 @@ extern "C" PyObject* Py_InitModule4(const char* name, PyMethodDef* methods, cons
while (methods && methods->ml_name) {
RELEASE_ASSERT((methods->ml_flags & (~(METH_VARARGS | METH_KEYWORDS | METH_NOARGS | METH_O))) == 0, "%d",
methods->ml_flags);
module->giveAttr(methods->ml_name, new BoxedCApiFunction(methods->ml_flags, passthrough, methods->ml_name,
methods->ml_meth, boxString(name)));
module->giveAttr(methods->ml_name, new BoxedCApiFunction(methods, passthrough, boxString(name)));
methods++;
}
......
......@@ -27,6 +27,8 @@ static const std::string _getattr_str("__getattr__");
static const std::string _getattribute_str("__getattribute__");
typedef int (*update_callback)(PyTypeObject*, void*);
static PyObject* tp_new_wrapper(PyTypeObject* self, BoxedTuple* args, Box* kwds) noexcept;
extern "C" void conservativeGCHandler(GCVisitor* v, Box* b) noexcept {
v->visitPotentialRange((void* const*)b, (void* const*)((char*)b + b->cls->tp_basicsize));
}
......@@ -1591,8 +1593,6 @@ static const slotdef* update_one_slot(BoxedClass* type, const slotdef* p) noexce
else
use_generic = 1;
}
// TODO Pyston doesn't support PyCFunction_Type yet I think?
#if 0
} else if (Py_TYPE(descr) == &PyCFunction_Type && PyCFunction_GET_FUNCTION(descr) == (PyCFunction)tp_new_wrapper
&& ptr == (void**)&type->tp_new) {
/* The __new__ wrapper is not a wrapper descriptor,
......@@ -1611,7 +1611,6 @@ static const slotdef* update_one_slot(BoxedClass* type, const slotdef* p) noexce
in this reasoning that requires additional
sanity checks. I'll buy the first person to
point out a bug in this reasoning a beer. */
#endif
} else if (offset == offsetof(BoxedClass, tp_descr_get) && descr->cls == function_cls
&& static_cast<BoxedFunction*>(descr)->f->always_use_version) {
type->tpp_descr_get = (descrgetfunc) static_cast<BoxedFunction*>(descr)->f->always_use_version->code;
......@@ -1745,12 +1744,16 @@ static PyObject* tp_new_wrapper(PyTypeObject* self, BoxedTuple* args, Box* kwds)
return self->tp_new(subtype, new_args, kwds);
}
static struct PyMethodDef tp_new_methoddef[] = { { "__new__", (PyCFunction)tp_new_wrapper, METH_VARARGS | METH_KEYWORDS,
PyDoc_STR("T.__new__(S, ...) -> "
"a new object with type S, a subtype of T") },
{ 0, 0, 0, 0 } };
static void add_tp_new_wrapper(BoxedClass* type) noexcept {
if (type->getattr("__new__"))
return;
type->giveAttr("__new__",
new BoxedCApiFunction(METH_VARARGS | METH_KEYWORDS, type, "__new__", (PyCFunction)tp_new_wrapper));
type->giveAttr("__new__", new BoxedCApiFunction(tp_new_methoddef, type));
}
void add_operators(BoxedClass* cls) noexcept {
......@@ -2931,7 +2934,7 @@ extern "C" void PyType_Modified(PyTypeObject* type) noexcept {
extern "C" int PyType_Ready(PyTypeObject* cls) noexcept {
ASSERT(!cls->is_pyston_class, "should not call this on Pyston classes");
gc::registerNonheapRootObject(cls);
gc::registerNonheapRootObject(cls, sizeof(PyTypeObject));
// unhandled fields:
int ALLOWABLE_FLAGS = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_CHECKTYPES
......
......@@ -37,24 +37,22 @@ struct wrapper_def {
extern "C" BoxedClass* capifunc_cls, *wrapperdescr_cls, *wrapperobject_cls;
class BoxedCApiFunction : public Box {
private:
int ml_flags;
Box* passthrough;
const char* name;
PyCFunction func;
public:
PyMethodDef* method_def;
PyObject* passthrough;
Box* module;
public:
BoxedCApiFunction(int ml_flags, Box* passthrough, const char* name, PyCFunction func, Box* module = NULL)
: ml_flags(ml_flags), passthrough(passthrough), name(name), func(func), module(module) {}
BoxedCApiFunction(PyMethodDef* method_def, Box* passthrough, Box* module = NULL)
: method_def(method_def), passthrough(passthrough), module(module) {}
DEFAULT_CLASS(capifunc_cls);
PyCFunction getFunction() { return method_def->ml_meth; }
static BoxedString* __repr__(BoxedCApiFunction* self) {
assert(self->cls == capifunc_cls);
return boxStrConstant(self->name);
return boxStrConstant(self->method_def->ml_name);
}
static Box* __call__(BoxedCApiFunction* self, BoxedTuple* varargs, BoxedDict* kwargs) {
......@@ -66,24 +64,28 @@ public:
threading::GLPromoteRegion _gil_lock;
Box* rtn;
if (self->ml_flags == METH_VARARGS) {
int flags = self->method_def->ml_flags;
auto func = self->method_def->ml_meth;
if (flags == METH_VARARGS) {
assert(kwargs->d.size() == 0);
rtn = (Box*)self->func(self->passthrough, varargs);
} else if (self->ml_flags == (METH_VARARGS | METH_KEYWORDS)) {
rtn = (Box*)((PyCFunctionWithKeywords)self->func)(self->passthrough, varargs, kwargs);
} else if (self->ml_flags == METH_NOARGS) {
rtn = (Box*)func(self->passthrough, varargs);
} else if (flags == (METH_VARARGS | METH_KEYWORDS)) {
rtn = (Box*)((PyCFunctionWithKeywords)func)(self->passthrough, varargs, kwargs);
} else if (flags == METH_NOARGS) {
assert(kwargs->d.size() == 0);
assert(varargs->size() == 0);
rtn = (Box*)self->func(self->passthrough, NULL);
} else if (self->ml_flags == METH_O) {
rtn = (Box*)func(self->passthrough, NULL);
} else if (flags == METH_O) {
if (kwargs->d.size() != 0) {
raiseExcHelper(TypeError, "%s() takes no keyword arguments", self->name);
raiseExcHelper(TypeError, "%s() takes no keyword arguments", self->method_def->ml_name);
}
if (varargs->size() != 1) {
raiseExcHelper(TypeError, "%s() takes exactly one argument (%d given)", self->name, varargs->size());
raiseExcHelper(TypeError, "%s() takes exactly one argument (%d given)", self->method_def->ml_name,
varargs->size());
}
rtn = (Box*)self->func(self->passthrough, varargs->elts[0]);
} else if (self->ml_flags == METH_OLDARGS) {
rtn = (Box*)func(self->passthrough, varargs->elts[0]);
} else if (flags == METH_OLDARGS) {
/* the really old style */
if (kwargs == NULL || PyDict_Size(kwargs) == 0) {
int size = PyTuple_GET_SIZE(varargs);
......@@ -92,12 +94,12 @@ public:
arg = PyTuple_GET_ITEM(varargs, 0);
else if (size == 0)
arg = NULL;
rtn = self->func(self->passthrough, arg);
rtn = func(self->passthrough, arg);
} else {
raiseExcHelper(TypeError, "%.200s() takes no keyword arguments", self->name);
raiseExcHelper(TypeError, "%.200s() takes no keyword arguments", self->method_def->ml_name);
}
} else {
RELEASE_ASSERT(0, "0x%x", self->ml_flags);
RELEASE_ASSERT(0, "0x%x", flags);
}
checkAndThrowCAPIException();
......@@ -107,7 +109,7 @@ public:
static Box* getname(Box* b, void*) {
RELEASE_ASSERT(b->cls == capifunc_cls, "");
const char* s = static_cast<BoxedCApiFunction*>(b)->name;
const char* s = static_cast<BoxedCApiFunction*>(b)->method_def->ml_name;
if (s)
return boxStrConstant(s);
return None;
......@@ -125,6 +127,10 @@ public:
v->visit(o->module);
}
};
static_assert(sizeof(BoxedCApiFunction) == sizeof(PyCFunctionObject), "");
static_assert(offsetof(BoxedCApiFunction, method_def) == offsetof(PyCFunctionObject, m_ml), "");
static_assert(offsetof(BoxedCApiFunction, passthrough) == offsetof(PyCFunctionObject, m_self), "");
static_assert(offsetof(BoxedCApiFunction, module) == offsetof(PyCFunctionObject, m_module), "");
class BoxedWrapperDescriptor : public Box {
public:
......
......@@ -390,6 +390,7 @@ enum class GCKind : uint8_t {
PRECISE = 3,
UNTRACKED = 4,
HIDDEN_CLASS = 5,
CONSERVATIVE_PYTHON = 6,
};
extern "C" void* gc_alloc(size_t nbytes, GCKind kind);
......
......@@ -162,12 +162,13 @@ static std::unordered_set<void*> nonheap_roots;
// way to verify it's not a nonheap root (the full check requires a hashtable lookup).
static void* max_nonheap_root = 0;
static void* min_nonheap_root = (void*)~0;
void registerNonheapRootObject(void* obj) {
void registerNonheapRootObject(void* obj, int size) {
// I suppose that things could work fine even if this were true, but why would it happen?
assert(global_heap.getAllocationFromInteriorPointer(obj) == NULL);
assert(nonheap_roots.count(obj) == 0);
nonheap_roots.insert(obj);
registerPotentialRootRange(obj, ((uint8_t*)obj) + size);
max_nonheap_root = std::max(obj, max_nonheap_root);
min_nonheap_root = std::min(obj, min_nonheap_root);
......@@ -179,10 +180,29 @@ bool isNonheapRoot(void* p) {
return nonheap_roots.count(p) != 0;
}
bool isValidGCObject(void* p) {
bool isValidGCMemory(void* p) {
return isNonheapRoot(p) || (global_heap.getAllocationFromInteriorPointer(p)->user_data == p);
}
bool isValidGCObject(void* p) {
if (isNonheapRoot(p))
return true;
GCAllocation* al = global_heap.getAllocationFromInteriorPointer(p);
if (!al)
return false;
return al->user_data == p && (al->kind_id == GCKind::CONSERVATIVE_PYTHON || al->kind_id == GCKind::PYTHON);
}
void setIsPythonObject(Box* b) {
auto al = global_heap.getAllocationFromInteriorPointer(b);
assert(al->user_data == (char*)b);
if (al->kind_id == GCKind::CONSERVATIVE) {
al->kind_id = GCKind::CONSERVATIVE_PYTHON;
} else {
assert(al->kind_id == GCKind::PYTHON);
}
}
static std::unordered_set<GCRootHandle*>* getRootHandles() {
static std::unordered_set<GCRootHandle*> root_handles;
return &root_handles;
......@@ -259,16 +279,6 @@ void markPhase() {
threading::visitAllStacks(&visitor);
gatherInterpreterRoots(&visitor);
for (void* p : nonheap_roots) {
Box* b = reinterpret_cast<Box*>(p);
BoxedClass* cls = b->cls;
if (cls) {
ASSERT(cls->gc_visit, "%s", getTypeName(b));
cls->gc_visit(&visitor, b);
}
}
for (auto h : *getRootHandles()) {
visitor.visit(h->value);
}
......@@ -289,7 +299,7 @@ void markPhase() {
GCKind kind_id = al->kind_id;
if (kind_id == GCKind::UNTRACKED) {
continue;
} else if (kind_id == GCKind::CONSERVATIVE) {
} else if (kind_id == GCKind::CONSERVATIVE || kind_id == GCKind::CONSERVATIVE_PYTHON) {
uint32_t bytes = al->kind_data;
if (DEBUG >= 2) {
if (global_heap.small_arena.contains(p)) {
......
......@@ -31,7 +31,8 @@ void deregisterPermanentRoot(void* root_obj);
// Register an object that was not allocated through this collector, as a root for this collector.
// The motivating usecase is statically-allocated PyTypeObject objects, which are full Python objects
// even if they are not heap allocated.
void registerNonheapRootObject(void* obj);
// This memory will be scanned conservatively.
void registerNonheapRootObject(void* obj, int size);
void registerPotentialRootRange(void* start, void* end);
......@@ -59,8 +60,10 @@ void disableGC();
void enableGC();
// These are mostly for debugging:
bool isValidGCObject(void* p);
bool isValidGCMemory(void* p); // if p is a valid gc-allocated pointer (or a non-heap root)
bool isValidGCObject(void* p); // whether p is valid gc memory and is set to have Python destructor semantics applied
bool isNonheapRoot(void* p);
void setIsPythonObject(Box* b);
// Debugging/validation helpers: if a GC should not happen in certain sections (ex during unwinding),
// use these functions to mark that. This is different from disableGC/enableGC, since it causes an
......
......@@ -152,7 +152,7 @@ bool _doFree(GCAllocation* al, std::vector<Box*>* weakly_referenced) {
VALGRIND_ENABLE_ERROR_REPORTING;
#endif
if (alloc_kind == GCKind::PYTHON) {
if (alloc_kind == GCKind::PYTHON || alloc_kind == GCKind::CONSERVATIVE_PYTHON) {
#ifndef NVALGRIND
VALGRIND_DISABLE_ERROR_REPORTING;
#endif
......@@ -170,7 +170,11 @@ bool _doFree(GCAllocation* al, std::vector<Box*>* weakly_referenced) {
}
}
ASSERT(b->cls->tp_dealloc == NULL, "%s", getTypeName(b));
// XXX: we are currently ignoring destructors (tp_dealloc) for extension objects, since we have
// historically done that (whoops) and there are too many to be worth changing for now as long
// as we can get real destructor support soon.
ASSERT(b->cls->tp_dealloc == NULL || alloc_kind == GCKind::CONSERVATIVE_PYTHON, "%s", getTypeName(b));
if (b->cls->simple_destructor)
b->cls->simple_destructor(b);
}
......@@ -208,7 +212,7 @@ struct HeapStatistics {
int num_hcls_by_attrs[HCLS_ATTRS_STAT_MAX + 1];
int num_hcls_by_attrs_exceed;
TypeStats python, conservative, untracked, hcls, precise;
TypeStats python, conservative, conservative_python, untracked, hcls, precise;
TypeStats total;
HeapStatistics(bool collect_cls_stats, bool collect_hcls_stats)
......@@ -247,6 +251,17 @@ void addStatistic(HeapStatistics* stats, GCAllocation* al, int nbytes) {
} else if (al->kind_id == GCKind::CONSERVATIVE) {
stats->conservative.nallocs++;
stats->conservative.nbytes += nbytes;
} else if (al->kind_id == GCKind::CONSERVATIVE_PYTHON) {
stats->conservative_python.nallocs++;
stats->conservative_python.nbytes += nbytes;
if (stats->collect_cls_stats) {
Box* b = (Box*)al->user_data;
auto& t = stats->by_cls[b->cls];
t.nallocs++;
t.nbytes += nbytes;
}
} else if (al->kind_id == GCKind::UNTRACKED) {
stats->untracked.nallocs++;
stats->untracked.nbytes += nbytes;
......@@ -288,6 +303,7 @@ void Heap::dumpHeapStatistics(int level) {
stats.python.print("python");
stats.conservative.print("conservative");
stats.conservative_python.print("conservative_python");
stats.untracked.print("untracked");
stats.hcls.print("hcls");
stats.precise.print("precise");
......@@ -454,7 +470,9 @@ SmallArena::Block** SmallArena::_freeChain(Block** head, std::vector<Box*>& weak
} else {
if (_doFree(al, &weakly_referenced)) {
b->isfree.set(atom_idx);
// memset(al->user_data, 0, b->size - sizeof(GCAllocation));
#ifndef NDEBUG
memset(al->user_data, 0xbb, b->size - sizeof(GCAllocation));
#endif
}
}
}
......
......@@ -498,7 +498,7 @@ void setupSys() {
sys_flags_cls->freeze();
for (auto& md : sys_methods) {
sys_module->giveAttr(md.ml_name, new BoxedCApiFunction(md.ml_flags, sys_module, md.ml_name, md.ml_meth));
sys_module->giveAttr(md.ml_name, new BoxedCApiFunction(&md, sys_module));
}
sys_module->giveAttr("flags", new BoxedSysFlags());
......
......@@ -27,6 +27,8 @@
using namespace pyston::threading;
extern "C" void initthread();
static int initialized;
static void PyThread__init_thread(void); /* Forward */
......@@ -57,8 +59,6 @@ static size_t _pythread_stacksize = 0;
namespace pyston {
BoxedModule* thread_module;
static void* thread_start(Box* target, Box* varargs, Box* kwargs) {
assert(target);
assert(varargs);
......@@ -156,73 +156,6 @@ Box* allocateLock() {
return new BoxedThreadLock();
}
static BoxedClass* thread_local_cls;
class BoxedThreadLocal : public Box {
public:
BoxedThreadLocal() {}
static Box* getThreadLocalObject(Box* obj) {
BoxedDict* dict = static_cast<BoxedDict*>(PyThreadState_GetDict());
Box* tls_obj = dict->getOrNull(obj);
if (tls_obj == NULL) {
tls_obj = new BoxedDict();
setitem(dict, obj, tls_obj);
}
return tls_obj;
}
static Box* setattrPyston(Box* obj, Box* name, Box* val) noexcept {
if (isSubclass(name->cls, str_cls) && static_cast<BoxedString*>(name)->s() == "__dict__") {
raiseExcHelper(AttributeError, "'%.50s' object attribute '__dict__' is read-only", Py_TYPE(obj)->tp_name);
}
Box* tls_obj = getThreadLocalObject(obj);
setitem(tls_obj, name, val);
return None;
}
static int setattro(Box* obj, Box* name, Box* val) noexcept {
try {
setattrPyston(obj, name, val);
} catch (ExcInfo e) {
setCAPIException(e);
return -1;
}
return 0;
}
static Box* getattro(Box* obj, Box* name) noexcept {
assert(name->cls == str_cls);
llvm::StringRef s = static_cast<BoxedString*>(name)->s();
Box* tls_obj = getThreadLocalObject(obj);
if (s == "__dict__")
return tls_obj;
try {
return getitem(tls_obj, name);
} catch (ExcInfo e) {
}
try {
Box* r = getattrInternalGeneric(obj, s, NULL, false, false, NULL, NULL);
if (r)
return r;
} catch (ExcInfo e) {
setCAPIException(e);
return NULL;
}
PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%.400s'", Py_TYPE(obj)->tp_name, s.data());
return NULL;
}
static Box* hash(Box* obj) { return boxInt(PyThread_get_thread_ident()); }
DEFAULT_CLASS(thread_local_cls);
};
Box* getIdent() {
return boxInt(pthread_self());
}
......@@ -232,7 +165,14 @@ Box* stackSize() {
}
void setupThread() {
thread_module = createModule("thread");
// Hacky: we want to use some of CPython's implementation of the thread module (the threading local stuff),
// and some of ours (thread handling). Start off by calling a cut-down version of initthread, and then
// add our own attributes to the module it creates.
initthread();
RELEASE_ASSERT(!PyErr_Occurred(), "");
Box* thread_module = getSysModulesDict()->getOrNull(boxString("thread"));
assert(thread_module);
thread_module->giveAttr("start_new_thread", new BoxedBuiltinFunctionOrMethod(
boxRTFunction((void*)startNewThread, BOXED_INT, 3, 1, false, false),
......@@ -256,21 +196,6 @@ void setupThread() {
thread_lock_cls->giveAttr("__exit__", new BoxedFunction(boxRTFunction((void*)BoxedThreadLock::exit, NONE, 4)));
thread_lock_cls->freeze();
thread_local_cls = new (0) BoxedHeapClass(object_cls, NULL, 0, 0, sizeof(BoxedThreadLocal), false,
static_cast<BoxedString*>(boxString("_local")));
thread_local_cls->giveAttr("__module__", boxStrConstant("thread"));
thread_local_cls->giveAttr("__hash__",
new BoxedFunction(boxRTFunction((void*)BoxedThreadLocal::hash, BOXED_INT, 1)));
thread_local_cls->giveAttr("__setattr__",
new BoxedFunction(boxRTFunction((void*)BoxedThreadLocal::setattrPyston, UNKNOWN, 3)));
thread_module->giveAttr("_local", thread_local_cls);
thread_local_cls->tp_setattro = BoxedThreadLocal::setattro;
thread_local_cls->tp_getattro = BoxedThreadLocal::getattro;
add_operators(thread_local_cls);
thread_local_cls->finishInitialization();
thread_local_cls->freeze();
BoxedClass* ThreadError
= BoxedHeapClass::create(type_cls, Exception, NULL, Exception->attrs_offset, Exception->tp_weaklistoffset,
Exception->tp_basicsize, false, "error");
......
......@@ -85,17 +85,6 @@ extern "C" void _PyErr_BadInternalCall(const char* filename, int lineno) noexcep
Py_FatalError("unimplemented");
}
extern "C" PyVarObject* PyObject_InitVar(PyVarObject* op, PyTypeObject* tp, Py_ssize_t size) noexcept {
assert(gc::isValidGCObject(op));
assert(gc::isValidGCObject(tp));
RELEASE_ASSERT(op, "");
RELEASE_ASSERT(tp, "");
Py_TYPE(op) = tp;
op->ob_size = size;
return op;
}
extern "C" PyObject* PyObject_Format(PyObject* obj, PyObject* format_spec) noexcept {
PyObject* empty = NULL;
PyObject* result = NULL;
......@@ -269,15 +258,215 @@ extern "C" PyObject* PyObject_GenericGetAttr(PyObject* o, PyObject* name) noexce
}
}
// Note (kmod): I don't feel great about including an alternate code-path for lookups. I also, however, don't feel
// great about modifying our code paths to take a custom dict, and since this code is just copied from CPython
// I feel like the risk is pretty low.
extern "C" PyObject* _PyObject_GenericGetAttrWithDict(PyObject* obj, PyObject* name, PyObject* dict) noexcept {
fatalOrError(PyExc_NotImplementedError, "unimplemented");
return nullptr;
PyTypeObject* tp = Py_TYPE(obj);
PyObject* descr = NULL;
PyObject* res = NULL;
descrgetfunc f;
Py_ssize_t dictoffset;
PyObject** dictptr;
if (!PyString_Check(name)) {
#ifdef Py_USING_UNICODE
/* The Unicode to string conversion is done here because the
existing tp_setattro slots expect a string object as name
and we wouldn't want to break those. */
if (PyUnicode_Check(name)) {
name = PyUnicode_AsEncodedString(name, NULL, NULL);
if (name == NULL)
return NULL;
} else
#endif
{
PyErr_Format(PyExc_TypeError, "attribute name must be string, not '%.200s'", Py_TYPE(name)->tp_name);
return NULL;
}
} else
Py_INCREF(name);
if (tp->tp_dict == NULL) {
if (PyType_Ready(tp) < 0)
goto done;
}
#if 0 /* XXX this is not quite _PyType_Lookup anymore */
/* Inline _PyType_Lookup */
{
Py_ssize_t i, n;
PyObject *mro, *base, *dict;
/* Look in tp_dict of types in MRO */
mro = tp->tp_mro;
assert(mro != NULL);
assert(PyTuple_Check(mro));
n = PyTuple_GET_SIZE(mro);
for (i = 0; i < n; i++) {
base = PyTuple_GET_ITEM(mro, i);
if (PyClass_Check(base))
dict = ((PyClassObject *)base)->cl_dict;
else {
assert(PyType_Check(base));
dict = ((PyTypeObject *)base)->tp_dict;
}
assert(dict && PyDict_Check(dict));
descr = PyDict_GetItem(dict, name);
if (descr != NULL)
break;
}
}
#else
descr = _PyType_Lookup(tp, name);
#endif
Py_XINCREF(descr);
f = NULL;
if (descr != NULL && PyType_HasFeature(descr->cls, Py_TPFLAGS_HAVE_CLASS)) {
f = descr->cls->tp_descr_get;
if (f != NULL && PyDescr_IsData(descr)) {
res = f(descr, obj, (PyObject*)obj->cls);
Py_DECREF(descr);
goto done;
}
}
if (dict == NULL) {
/* Inline _PyObject_GetDictPtr */
dictoffset = tp->tp_dictoffset;
if (dictoffset != 0) {
if (dictoffset < 0) {
Py_ssize_t tsize;
size_t size;
tsize = ((PyVarObject*)obj)->ob_size;
if (tsize < 0)
tsize = -tsize;
size = _PyObject_VAR_SIZE(tp, tsize);
dictoffset += (long)size;
assert(dictoffset > 0);
assert(dictoffset % SIZEOF_VOID_P == 0);
}
dictptr = (PyObject**)((char*)obj + dictoffset);
dict = *dictptr;
}
}
if (dict != NULL) {
Py_INCREF(dict);
res = PyDict_GetItem(dict, name);
if (res != NULL) {
Py_INCREF(res);
Py_XDECREF(descr);
Py_DECREF(dict);
goto done;
}
Py_DECREF(dict);
}
if (f != NULL) {
res = f(descr, obj, (PyObject*)Py_TYPE(obj));
Py_DECREF(descr);
goto done;
}
if (descr != NULL) {
res = descr;
/* descr was already increfed above */
goto done;
}
PyErr_Format(PyExc_AttributeError, "'%.50s' object has no attribute '%.400s'", tp->tp_name,
PyString_AS_STRING(name));
done:
Py_DECREF(name);
return res;
}
// (see note for _PyObject_GenericGetAttrWithDict)
extern "C" int _PyObject_GenericSetAttrWithDict(PyObject* obj, PyObject* name, PyObject* value,
PyObject* dict) noexcept {
fatalOrError(PyExc_NotImplementedError, "unimplemented");
return -1;
PyTypeObject* tp = Py_TYPE(obj);
PyObject* descr;
descrsetfunc f;
PyObject** dictptr;
int res = -1;
if (!PyString_Check(name)) {
#ifdef Py_USING_UNICODE
/* The Unicode to string conversion is done here because the
existing tp_setattro slots expect a string object as name
and we wouldn't want to break those. */
if (PyUnicode_Check(name)) {
name = PyUnicode_AsEncodedString(name, NULL, NULL);
if (name == NULL)
return -1;
} else
#endif
{
PyErr_Format(PyExc_TypeError, "attribute name must be string, not '%.200s'", Py_TYPE(name)->tp_name);
return -1;
}
} else
Py_INCREF(name);
if (tp->tp_dict == NULL) {
if (PyType_Ready(tp) < 0)
goto done;
}
descr = _PyType_Lookup(tp, name);
f = NULL;
if (descr != NULL && PyType_HasFeature(descr->cls, Py_TPFLAGS_HAVE_CLASS)) {
f = descr->cls->tp_descr_set;
if (f != NULL && PyDescr_IsData(descr)) {
res = f(descr, obj, value);
goto done;
}
}
if (dict == NULL) {
dictptr = _PyObject_GetDictPtr(obj);
if (dictptr != NULL) {
dict = *dictptr;
if (dict == NULL && value != NULL) {
dict = PyDict_New();
if (dict == NULL)
goto done;
*dictptr = dict;
}
}
}
if (dict != NULL) {
Py_INCREF(dict);
if (value == NULL)
res = PyDict_DelItem(dict, name);
else
res = PyDict_SetItem(dict, name, value);
if (res < 0 && PyErr_ExceptionMatches(PyExc_KeyError))
PyErr_SetObject(PyExc_AttributeError, name);
Py_DECREF(dict);
goto done;
}
if (f != NULL) {
res = f(descr, obj, value);
goto done;
}
if (descr == NULL) {
PyErr_Format(PyExc_AttributeError, "'%.100s' object has no attribute '%.200s'", tp->tp_name,
PyString_AS_STRING(name));
goto done;
}
PyErr_Format(PyExc_AttributeError, "'%.50s' object attribute '%.400s' is read-only", tp->tp_name,
PyString_AS_STRING(name));
done:
Py_DECREF(name);
return res;
}
......@@ -1265,7 +1454,7 @@ extern "C" PyObject* Py_FindMethod(PyMethodDef* methods, PyObject* self, const c
extern "C" PyObject* PyCFunction_NewEx(PyMethodDef* ml, PyObject* self, PyObject* module) noexcept {
assert((ml->ml_flags & (~(METH_VARARGS | METH_KEYWORDS | METH_NOARGS | METH_O))) == 0);
return new BoxedCApiFunction(ml->ml_flags, self, ml->ml_name, ml->ml_meth, module);
return new BoxedCApiFunction(ml, self, module);
}
extern "C" PyCFunction PyCFunction_GetFunction(PyObject* op) noexcept {
......@@ -1273,7 +1462,15 @@ extern "C" PyCFunction PyCFunction_GetFunction(PyObject* op) noexcept {
PyErr_BadInternalCall();
return NULL;
}
return ((PyCFunctionObject*)op)->m_ml->ml_meth;
return static_cast<BoxedCApiFunction*>(op)->getFunction();
}
extern "C" PyObject* PyCFunction_GetSelf(PyObject* op) noexcept {
if (!PyCFunction_Check(op)) {
PyErr_BadInternalCall();
return NULL;
}
return static_cast<BoxedCApiFunction*>(op)->passthrough;
}
extern "C" int _PyEval_SliceIndex(PyObject* v, Py_ssize_t* pi) noexcept {
......@@ -1413,18 +1610,18 @@ Box* BoxedCApiFunction::callInternal(BoxedFunctionBase* func, CallRewriteArgs* r
assert(arg1->cls == capifunc_cls);
BoxedCApiFunction* capifunc = static_cast<BoxedCApiFunction*>(arg1);
if (capifunc->ml_flags != METH_O)
if (capifunc->method_def->ml_flags != METH_O)
return callFunc(func, rewrite_args, argspec, arg1, arg2, arg3, args, keyword_names);
if (rewrite_args) {
rewrite_args->arg1->addGuard((intptr_t)arg1);
RewriterVar* r_passthrough = rewrite_args->arg1->getAttr(offsetof(BoxedCApiFunction, passthrough));
rewrite_args->out_rtn
= rewrite_args->rewriter->call(true, (void*)capifunc->func, r_passthrough, rewrite_args->arg2);
rewrite_args->out_rtn = rewrite_args->rewriter->call(true, (void*)capifunc->method_def->ml_meth, r_passthrough,
rewrite_args->arg2);
rewrite_args->rewriter->call(true, (void*)checkAndThrowCAPIException);
rewrite_args->out_success = true;
}
Box* r = capifunc->func(capifunc->passthrough, arg2);
Box* r = capifunc->method_def->ml_meth(capifunc->passthrough, arg2);
checkAndThrowCAPIException();
assert(r);
return r;
......@@ -1448,7 +1645,7 @@ static Box* methodGetDoc(Box* b, void*) {
extern "C" PyObject* _PyObject_GC_Malloc(size_t basicsize) noexcept {
Box* r = ((PyObject*)PyObject_MALLOC(basicsize));
RELEASE_ASSERT(gc::isValidGCObject(r), "");
RELEASE_ASSERT(gc::isValidGCMemory(r), "");
return r;
}
......
......@@ -901,6 +901,16 @@ void Box::setattr(const std::string& attr, Box* val, SetattrRewriteArgs* rewrite
abort();
}
extern "C" PyObject* _PyType_Lookup(PyTypeObject* type, PyObject* name) noexcept {
RELEASE_ASSERT(name->cls == str_cls, "");
try {
return typeLookup(type, static_cast<BoxedString*>(name)->s(), NULL);
} catch (ExcInfo e) {
setCAPIException(e);
return NULL;
}
}
Box* typeLookup(BoxedClass* cls, const std::string& attr, GetattrRewriteArgs* rewrite_args) {
Box* val;
......@@ -2328,7 +2338,7 @@ extern "C" void dumpEx(void* p, int levels) {
printf("\n");
printf("Raw address: %p\n", p);
bool is_gc = gc::isValidGCObject(p);
bool is_gc = gc::isValidGCMemory(p);
if (!is_gc) {
printf("non-gc memory\n");
return;
......@@ -4513,23 +4523,10 @@ Box* typeCallInternal(BoxedFunctionBase* f, CallRewriteArgs* rewrite_args, ArgPa
// Notably, "type" itself does not. For instance, assuming M is a subclass of
// type, type.__new__(M, 1) will return the int class, which is not an instance of M.
static Box* object_new = NULL;
static Box* object_init = NULL;
// this is ok with not using StlCompatAllocator since we will manually register these objects with the GC
static std::vector<Box*> allowable_news;
if (!object_new) {
object_new = typeLookup(object_cls, new_str, NULL);
assert(object_new);
// I think this is unnecessary, but good form:
gc::registerPermanentRoot(object_new);
object_init = typeLookup(object_cls, init_str, NULL);
assert(object_init);
gc::registerPermanentRoot(object_init);
allowable_news.push_back(object_new);
for (BoxedClass* allowed_cls : { enumerate_cls, xrange_cls }) {
if (allowable_news.empty()) {
for (BoxedClass* allowed_cls : { object_cls, enumerate_cls, xrange_cls }) {
auto new_obj = typeLookup(allowed_cls, new_str, NULL);
gc::registerPermanentRoot(new_obj);
allowable_news.push_back(new_obj);
......@@ -4585,16 +4582,9 @@ Box* typeCallInternal(BoxedFunctionBase* f, CallRewriteArgs* rewrite_args, ArgPa
RewriterVar* r_made = NULL;
ArgPassSpec new_argspec = argspec;
if (npassed_args > 1 && new_attr == object_new) {
if (init_attr == object_init) {
raiseExcHelper(TypeError, objectNewParameterTypeErrorMsg());
} else {
new_argspec = ArgPassSpec(1);
}
}
if (rewrite_args) {
if (new_attr == object_new && init_attr != object_init) {
if (cls->tp_new == object_cls->tp_new && cls->tp_init != object_cls->tp_init) {
// Fast case: if we are calling object_new, we normally doesn't look at the arguments at all.
// (Except in the case when init_attr != object_init, in which case object_new looks at the number
// of arguments and throws an exception.)
......@@ -4660,7 +4650,7 @@ Box* typeCallInternal(BoxedFunctionBase* f, CallRewriteArgs* rewrite_args, ArgPa
}
}
if (init_attr && init_attr != object_init) {
if (init_attr && made->cls->tp_init != object_cls->tp_init) {
// TODO apply the same descriptor special-casing as in callattr?
Box* initrtn;
......@@ -4722,8 +4712,10 @@ Box* typeCallInternal(BoxedFunctionBase* f, CallRewriteArgs* rewrite_args, ArgPa
Box* typeCall(Box* obj, BoxedTuple* vararg, BoxedDict* kwargs) {
assert(vararg->cls == tuple_cls);
bool pass_kwargs = (kwargs && kwargs->d.size());
int n = vararg->size();
int args_to_pass = n + 2; // 1 for obj, 1 for kwargs
int args_to_pass = n + 1 + (pass_kwargs ? 1 : 0); // 1 for obj, 1 for kwargs
Box** args = NULL;
if (args_to_pass > 3)
......@@ -4734,9 +4726,11 @@ Box* typeCall(Box* obj, BoxedTuple* vararg, BoxedDict* kwargs) {
for (int i = 0; i < n; i++) {
getArg(i + 1, arg1, arg2, arg3, args) = vararg->elts[i];
}
getArg(n + 1, arg1, arg2, arg3, args) = kwargs;
return typeCallInternal(NULL, NULL, ArgPassSpec(n + 1, 0, false, true), arg1, arg2, arg3, args, NULL);
if (pass_kwargs)
getArg(n + 1, arg1, arg2, arg3, args) = kwargs;
return typeCallInternal(NULL, NULL, ArgPassSpec(n + 1, 0, false, pass_kwargs), arg1, arg2, arg3, args, NULL);
}
extern "C" void delGlobal(Box* globals, const std::string* name) {
......
......@@ -95,7 +95,8 @@ bool IN_SHUTDOWN = false;
extern "C" PyObject* PystonType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems) noexcept {
assert(cls);
const size_t size = _PyObject_VAR_SIZE(cls, nitems);
// See PyType_GenericAlloc for note about the +1 here:
const size_t size = _PyObject_VAR_SIZE(cls, nitems + 1);
#ifndef NDEBUG
#if 0
......@@ -144,8 +145,11 @@ extern "C" PyObject* PystonType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems)
extern "C" PyObject* PyType_GenericAlloc(PyTypeObject* type, Py_ssize_t nitems) noexcept {
PyObject* obj;
const size_t size = _PyObject_VAR_SIZE(type, nitems);
const size_t size = _PyObject_VAR_SIZE(type, nitems + 1);
// I don't understand why there is a +1 in this method; _PyObject_NewVar doesn't do that.
// CPython has the following comment:
/* note that we need to add one, for the sentinel */
// I think that regardless of the reasoning behind them having it, we should do what they do?
if (PyType_IS_GC(type))
obj = _PyObject_GC_Malloc(size);
......@@ -1731,26 +1735,45 @@ Box* objectNewNoArgs(BoxedClass* cls) {
return new (cls) Box();
}
Box* objectNew(BoxedClass* cls, BoxedTuple* args, BoxedDict* kwargs) {
assert(isSubclass(cls->cls, type_cls));
assert(args->cls == tuple_cls);
assert(kwargs->cls == dict_cls);
static int excess_args(PyObject* args, PyObject* kwds) noexcept {
return PyTuple_GET_SIZE(args) || (kwds && PyDict_Check(kwds) && PyDict_Size(kwds));
}
// We use a different strategy from CPython: we let object.__new__ take extra
// arguments, but raise an error if they wouldn't be handled by the corresponding init.
// TODO switch to the CPython approach?
if (args->size() != 0 || kwargs->d.size() != 0) {
// TODO slow (We already cache these in typeCall -- should use that here too?)
if (typeLookup(cls, "__new__", NULL) != typeLookup(object_cls, "__new__", NULL)
|| typeLookup(cls, "__init__", NULL) == typeLookup(object_cls, "__init__", NULL))
raiseExcHelper(TypeError, objectNewParameterTypeErrorMsg());
}
static PyObject* object_new(PyTypeObject* type, PyObject* args, PyObject* kwds) noexcept;
return new (cls) Box();
static int object_init(PyObject* self, PyObject* args, PyObject* kwds) noexcept {
int err = 0;
if (excess_args(args, kwds)) {
PyTypeObject* type = Py_TYPE(self);
if (type->tp_init != object_init && type->tp_new != object_new) {
err = PyErr_WarnEx(PyExc_DeprecationWarning, "object.__init__() takes no parameters", 1);
} else if (type->tp_init != object_init || type->tp_new == object_new) {
PyErr_SetString(PyExc_TypeError, "object.__init__() takes no parameters");
err = -1;
}
}
return err;
}
Box* objectInit(Box* b, BoxedTuple* args) {
return None;
static PyObject* object_new(PyTypeObject* type, PyObject* args, PyObject* kwds) noexcept {
int err = 0;
if (excess_args(args, kwds)) {
if (type->tp_new != object_new && type->tp_init != object_init) {
err = PyErr_WarnEx(PyExc_DeprecationWarning, "object() takes no parameters", 1);
} else if (type->tp_new != object_new || type->tp_init == object_init) {
PyErr_SetString(PyExc_TypeError, "object() takes no parameters");
err = -1;
}
}
if (err < 0)
return NULL;
if (type->tp_flags & Py_TPFLAGS_IS_ABSTRACT) {
// I don't know what this is or when it happens, but
// CPython does something special with it
Py_FatalError("unimplemented");
}
return type->tp_alloc(type, 0);
}
Box* objectRepr(Box* obj) {
......@@ -2209,13 +2232,30 @@ inline void initUserAttrs(Box* obj, BoxedClass* cls) {
extern "C" void PyCallIter_AddHasNext();
extern "C" PyVarObject* PyObject_InitVar(PyVarObject* op, PyTypeObject* tp, Py_ssize_t size) noexcept {
assert(op);
assert(tp);
assert(gc::isValidGCMemory(op));
assert(gc::isValidGCObject(tp));
gc::setIsPythonObject(op);
Py_TYPE(op) = tp;
op->ob_size = size;
return op;
}
extern "C" PyObject* PyObject_Init(PyObject* op, PyTypeObject* tp) noexcept {
assert(op);
assert(tp);
assert(gc::isValidGCObject(op));
assert(gc::isValidGCMemory(op));
assert(gc::isValidGCObject(tp));
gc::setIsPythonObject(op);
Py_TYPE(op) = tp;
if (PyType_SUPPORTS_WEAKREFS(tp)) {
......@@ -2428,6 +2468,8 @@ void setupRuntime() {
object_cls->tp_getattro = PyObject_GenericGetAttr;
object_cls->tp_setattro = PyObject_GenericSetAttr;
object_cls->tp_init = object_init;
object_cls->tp_new = object_new;
add_operators(object_cls);
object_cls->finishInitialization();
......@@ -2492,8 +2534,6 @@ void setupRuntime() {
SET = typeFromClass(set_cls);
FROZENSET = typeFromClass(frozenset_cls);
object_cls->giveAttr("__new__", new BoxedFunction(boxRTFunction((void*)objectNew, UNKNOWN, 1, 0, true, true)));
object_cls->giveAttr("__init__", new BoxedFunction(boxRTFunction((void*)objectInit, UNKNOWN, 1, 0, true, false)));
object_cls->giveAttr("__repr__", new BoxedFunction(boxRTFunction((void*)objectRepr, UNKNOWN, 1, 0, false, false)));
object_cls->giveAttr("__str__", new BoxedFunction(boxRTFunction((void*)objectStr, UNKNOWN, 1, 0, false, false)));
object_cls->giveAttr("__hash__",
......@@ -2546,6 +2586,8 @@ void setupRuntime() {
}
object_cls->giveAttr("__class__", new (pyston_getset_cls) BoxedGetsetDescriptor(objectClass, objectSetClass, NULL));
object_cls->freeze();
assert(object_cls->tp_init == object_init);
assert(object_cls->tp_new == object_new);
setupBool();
setupLong();
......
# should_error
# skip-if: sys.version_info < (2, 7, 4)
# - Error message changed in 2.7.4
# Regression test:
# If the init function doesn't exist, shouldn't just silently ignore any args
# that got passed
......@@ -16,5 +12,8 @@ print "This should have worked"
class D(object):
pass
d = D(1)
print "This should have failed"
try:
d = D(1)
print "This should have failed"
except TypeError as e:
print "expected exception" # the message got changed in 2.7.4
# should_error
# skip-if: sys.version_info < (2, 7, 4)
# - Error message changed in 2.7.4
# object.__new__ doesn't complain if __init__ is overridden:
class C1(object):
......@@ -16,4 +12,12 @@ object.__new__(C1, 1)
object.__new__(C1, a=1)
print "Trying C2"
object.__new__(C2, 1)
try:
object.__new__(C2, 1)
except TypeError as e:
print "caught TypeError"
# These are some tricky cases, since they can potentially look like arguments
# are being passed, but really they are not.
type.__call__(*[C2])
type.__call__(C2, **{})
......@@ -12,12 +12,14 @@ class CustomThreadingLocal(threading.local):
self.n += 1
print self.a, self.n
print CustomThreadingLocal().a
print CustomThreadingLocal().a
c = CustomThreadingLocal()
print c.a
def f():
print
a.x = "goodbye world"
print a.x
print CustomThreadingLocal().a
print c.a
print CustomThreadingLocal().a
def test():
......@@ -36,3 +38,6 @@ print a.x
a.__setattr__('x', 5)
print a.x
print sorted(a.__dict__.items())
del a.x
print sorted(a.__dict__.items())
# Make sure we can support weakrefs on extension objects.
# The _sre.SRE_Pattern type is one of the few builtin types that supports weakrefs natively.
import _sre
def f(n):
if n:
return f(n-1)
p = _sre.compile('', 0, [1])
print type(p)
return weakref.ref(p)
import weakref
r = f(20)
import gc
gc.collect()
print r()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment