Commits

Amaury Forgeot d'Arc committed 56ffa1a

The NUL byte is now disallowed in many posix functions.
We use the annotator to "prove" that a string has no NUL.

Comments (0)

Files changed (21)

pypy/annotation/binaryop.py

 class __extend__(pairtype(SomeString, SomeString)):
 
     def union((str1, str2)):
-        return SomeString(can_be_None=str1.can_be_None or str2.can_be_None)
+        result = SomeString(can_be_None=str1.can_be_None or str2.can_be_None)
+        if str1.no_NUL and str2.no_NUL:
+            result.no_NUL = True
+        return result
 
     def add((str1, str2)):
         # propagate const-ness to help getattr(obj, 'prefix' + const_name)
         result = SomeString()
         if str1.is_immutable_constant() and str2.is_immutable_constant():
             result.const = str1.const + str2.const
+        if str1.no_NUL and str2.no_NUL:
+            result.no_NUL = True
         return result
 
 class __extend__(pairtype(SomeChar, SomeChar)):
                 raise NotImplementedError(
                     "string formatting mixing strings and unicode not supported")
         getbookkeeper().count('strformat', str, s_tuple)
-        return SomeString()
+        result = SomeString()
+        no_NUL = str.no_NUL
+        for s_item in s_tuple.items:
+            if isinstance(s_item, SomeFloat):
+                pass
+            elif isinstance(s_item, SomeString) and s_item.no_NUL:
+                pass
+            else:
+                no_NUL = False
+                break
+        if no_NUL:
+            result.no_NUL = True
+        return result
 
 
 class __extend__(pairtype(SomeString, SomeObject)):
 
 # mixing Nones with other objects
 
-def _make_none_union(classname, constructor_args='', glob=None):
+def _make_none_union(classname, constructor_args='', glob=None,
+                     copy_attributes=()):
+    if copy_attributes:
+        copy_attrs = (
+            'result.__dict__.update((name, getattr(obj, name)) '
+            'for name in %(copy_attributes)s if name in obj.__dict__)'
+            % locals())
+    else:
+        copy_attrs = ''
+
     if glob is None:
         glob = globals()
     loc = locals()
         class __extend__(pairtype(%(classname)s, SomePBC)):
             def union((obj, pbc)):
                 if pbc.isNone():
-                    return %(classname)s(%(constructor_args)s)
+                    result = %(classname)s(%(constructor_args)s)
+                    %(copy_attrs)s
+                    return result
                 else:
                     return SomeObject()
 
         class __extend__(pairtype(SomePBC, %(classname)s)):
             def union((pbc, obj)):
                 if pbc.isNone():
-                    return %(classname)s(%(constructor_args)s)
+                    result = %(classname)s(%(constructor_args)s)
+                    %(copy_attrs)s
+                    return result
                 else:
                     return SomeObject()
     """ % loc)
     exec source.compile() in glob
 
 _make_none_union('SomeInstance',   'classdef=obj.classdef, can_be_None=True')
-_make_none_union('SomeString',      'can_be_None=True')
+_make_none_union('SomeString',      'can_be_None=True',
+                 copy_attributes=('no_NUL',))
 _make_none_union('SomeUnicodeString', 'can_be_None=True')
 _make_none_union('SomeList',         'obj.listdef')
 _make_none_union('SomeDict',          'obj.dictdef')

pypy/annotation/bookkeeper.py

         elif issubclass(tp, str): # py.lib uses annotated str subclasses
             if len(x) == 1:
                 result = SomeChar()
+                if not '\x00' in x:
+                    result.no_NUL = True
             else:
                 result = SomeString()
+                if not '\x00' in x:
+                    result.no_NUL = True
         elif tp is unicode:
             if len(x) == 1:
                 result = SomeUnicodeCodePoint()

pypy/annotation/model.py

     "Stands for an object which is known to be a string."
     knowntype = str
     immutable = True
+    can_be_None=False
+    no_NUL = False
+
     def __init__(self, can_be_None=False):
-        self.can_be_None = can_be_None
+        if can_be_None:
+            self.can_be_None = True
 
     def can_be_none(self):
         return self.can_be_None
 
     def nonnoneify(self):
-        return SomeString(can_be_None=False)
+        result = SomeString(can_be_None=False)
+        if self.no_NUL:
+            result.no_NUL = True
+        return result
 
 class SomeUnicodeString(SomeObject):
     "Stands for an object which is known to be an unicode string"
     knowntype = unicode
     immutable = True
+    no_NUL = False
     def __init__(self, can_be_None=False):
         self.can_be_None = can_be_None
 
 s_None = SomePBC([], can_be_None=True)
 s_Bool = SomeBool()
 s_ImpossibleValue = SomeImpossibleValue()
+s_Str0 = SomeString()
+s_Str0.no_NUL = True
 
 # ____________________________________________________________
 # weakrefs
 
 def not_const(s_obj):
     if s_obj.is_constant():
-        new_s_obj = SomeObject()
-        new_s_obj.__class__ = s_obj.__class__
+        new_s_obj = SomeObject.__new__(s_obj.__class__)
         dic = new_s_obj.__dict__ = s_obj.__dict__.copy()
         if 'const' in dic:
             del new_s_obj.const

pypy/annotation/test/test_annrpython.py

             return ''.join(g(n))
         s = a.build_types(f, [int])
         assert s.knowntype == str
+        assert s.no_NUL
+
+    def test_str_split(self):
+        a = self.RPythonAnnotator()
+        def g(n):
+            if n:
+                return "test string"
+        def f(n):
+            if n:
+                return g(n).split(' ')
+        s = a.build_types(f, [int])
+        assert isinstance(s, annmodel.SomeList)
+        s_item = s.listdef.listitem.s_value
+        assert s_item.no_NUL
 
     def test_str_splitlines(self):
         a = self.RPythonAnnotator()
             return obj.indirect()
         a = self.RPythonAnnotator()
         s = a.build_types(f, [bool])
-        assert s == annmodel.SomeString(can_be_None=True)
+        assert annmodel.SomeString(can_be_None=True).contains(s)
 
     def test_dont_see_AttributeError_clause(self):
         class Stuff:
         s = a.build_types(g, [int])
         assert not s.can_be_None
 
+    def test_string_noNUL_canbeNone(self):
+        def f(a):
+            if a:
+                return "abc"
+            else:
+                return None
+        a = self.RPythonAnnotator()
+        s = a.build_types(f, [int])
+        assert s.can_be_None
+        assert s.no_NUL
+
+    def test_str_or_None(self):
+        def f(a):
+            if a:
+                return "abc"
+            else:
+                return None
+        def g(a):
+            x = f(a)
+            #assert x is not None
+            if x is None:
+                return "abcd"
+            return x
+            if isinstance(x, str):
+                return x
+            return "impossible"
+        a = self.RPythonAnnotator()
+        s = a.build_types(f, [int])
+        assert s.can_be_None
+        assert s.no_NUL
+
     def test_emulated_pbc_call_simple(self):
         def f(a,b):
             return a + b
         assert isinstance(s, annmodel.SomeIterator)
         assert s.variant == ('items',)
 
+    def test_iteritems_str0(self):
+        def it(d):
+            return d.iteritems()
+        def f():
+            d0 = {'1a': '2a', '3': '4'}
+            for item in it(d0):
+                return "%s=%s" % item
+            raise ValueError
+        a = self.RPythonAnnotator()
+        s = a.build_types(f, [])
+        assert isinstance(s, annmodel.SomeString)
+        assert s.no_NUL
+
     def test_non_none_and_none_with_isinstance(self):
         class A(object):
             pass

pypy/annotation/unaryop.py

             if isinstance(str, SomeUnicodeString):
                 return immutablevalue(u"")
             return immutablevalue("")
-        return str.basestringclass()
+        result = str.basestringclass()
+        if str.no_NUL and s_item.no_NUL:
+            result.no_NUL = True
+        return result
 
     def iter(str):
         return SomeIterator(str)
 
     def method_split(str, patt, max=-1):
         getbookkeeper().count("str_split", str, patt)
-        return getbookkeeper().newlist(str.basestringclass())
+        s_item = str.basestringclass()
+        if str.no_NUL:
+            s_item.no_NUL = True
+        result = getbookkeeper().newlist(s_item)
+        return result
 
     def method_rsplit(str, patt, max=-1):
         getbookkeeper().count("str_rsplit", str, patt)
-        return getbookkeeper().newlist(str.basestringclass())
+        result = getbookkeeper().newlist(str.basestringclass())
+        if str.no_NUL:
+            result.no_NUL = True
+        return result
 
     def method_replace(str, s1, s2):
         return str.basestringclass()
 
     def getslice(str, s_start, s_stop):
         check_negative_slice(s_start, s_stop)
-        return str.basestringclass()
+        result = str.basestringclass()
+        if str.no_NUL:
+            result.no_NUL = True
+        return result
 
 class __extend__(SomeUnicodeString):
     def method_encode(uni, s_enc):

pypy/interpreter/baseobjspace.py

     def str_w(self, w_obj):
         return w_obj.str_w(self)
 
+    def str0_w(self, w_obj):
+        "Like str_w, but rejects strings with NUL bytes."
+        from pypy.rlib import rstring
+        result = w_obj.str_w(self)
+        if '\x00' in result:
+            raise OperationError(self.w_TypeError, self.wrap(
+                    'argument must be a string without NUL characters'))
+        return rstring.assert_str0(result)
+
     def int_w(self, w_obj):
         return w_obj.int_w(self)
 

pypy/interpreter/gateway.py

     def visit_str_or_None(self, el, app_sig):
         self.checked_space_method(el, app_sig)
 
+    def visit_str0(self, el, app_sig):
+        self.checked_space_method(el, app_sig)
+
     def visit_nonnegint(self, el, app_sig):
         self.checked_space_method(el, app_sig)
 
     def visit_str_or_None(self, typ):
         self.run_args.append("space.str_or_None_w(%s)" % (self.scopenext(),))
 
+    def visit_str0(self, typ):
+        self.run_args.append("space.str0_w(%s)" % (self.scopenext(),))
+
     def visit_nonnegint(self, typ):
         self.run_args.append("space.gateway_nonnegint_w(%s)" % (
             self.scopenext(),))
     def visit_str_or_None(self, typ):
         self.unwrap.append("space.str_or_None_w(%s)" % (self.nextarg(),))
 
+    def visit_str0(self, typ):
+        self.unwrap.append("space.str0_w(%s)" % (self.nextarg(),))
+
     def visit_nonnegint(self, typ):
         self.unwrap.append("space.gateway_nonnegint_w(%s)" % (self.nextarg(),))
 

pypy/interpreter/mixedmodule.py

             space.call_method(self.w_dict, 'update', self.w_initialdict)
 
         for w_submodule in self.submodules_w:
-            name = space.str_w(w_submodule.w_name)
+            name = space.str0_w(w_submodule.w_name)
             space.setitem(self.w_dict, space.wrap(name.split(".")[-1]), w_submodule)
             space.getbuiltinmodule(name)
 

pypy/interpreter/module.py

     def install(self):
         """NOT_RPYTHON: installs this module into space.builtin_modules"""
         w_mod = self.space.wrap(self)
-        self.space.builtin_modules[self.space.unwrap(self.w_name)] = w_mod
+        modulename = self.space.str0_w(self.w_name)
+        self.space.builtin_modules[modulename] = w_mod
 
     def setup_after_space_initialization(self):
         """NOT_RPYTHON: to allow built-in modules to do some more setup

pypy/module/bz2/interp_bz2.py

     if basemode == "a":
         raise OperationError(space.w_ValueError,
                              space.wrap("cannot append to bz2 file"))
-    stream = open_path_helper(space.str_w(w_path), os_flags, False)
+    stream = open_path_helper(space.str0_w(w_path), os_flags, False)
     if reading:
         bz2stream = ReadBZ2Filter(space, stream, buffering)
         buffering = 0     # by construction, the ReadBZ2Filter acts like

pypy/module/gc/interp_gc.py

 
 # ____________________________________________________________
 
-@unwrap_spec(filename=str)
+@unwrap_spec(filename='str0')
 def dump_heap_stats(space, filename):
     tb = rgc._heap_stats()
     if not tb:

pypy/module/imp/importing.py

     ctxt_package = None
     if ctxt_w_package is not None and ctxt_w_package is not space.w_None:
         try:
-            ctxt_package = space.str_w(ctxt_w_package)
+            ctxt_package = space.str0_w(ctxt_w_package)
         except OperationError, e:
             if not e.match(space, space.w_TypeError):
                 raise
         ctxt_name = None
         if ctxt_w_name is not None:
             try:
-                ctxt_name = space.str_w(ctxt_w_name)
+                ctxt_name = space.str0_w(ctxt_w_name)
             except OperationError, e:
                 if not e.match(space, space.w_TypeError):
                     raise
     return rel_modulename, rel_level
 
 
-@unwrap_spec(name=str, level=int)
+@unwrap_spec(name='str0', level=int)
 def importhook(space, name, w_globals=None,
                w_locals=None, w_fromlist=None, level=-1):
     modulename = name
                     fromlist_w = space.fixedview(w_all)
             for w_name in fromlist_w:
                 if try_getattr(space, w_mod, w_name) is None:
-                    load_part(space, w_path, prefix, space.str_w(w_name), w_mod,
-                              tentative=1)
+                    load_part(space, w_path, prefix, space.str0_w(w_name),
+                              w_mod, tentative=1)
         return w_mod
     else:
         return first
     def __init__(self, space):
         pass
 
-    @unwrap_spec(path=str)
+    @unwrap_spec(path='str0')
     def descr_init(self, space, path):
         if not path:
             raise OperationError(space.w_ImportError, space.wrap(
                 if w_loader:
                     return FindInfo.fromLoader(w_loader)
 
-            path = space.str_w(w_pathitem)
+            path = space.str0_w(w_pathitem)
             filepart = os.path.join(path, partname)
             if os.path.isdir(filepart) and case_ok(filepart):
                 initfile = os.path.join(filepart, '__init__')
             space.wrap("reload() argument must be module"))
 
     w_modulename = space.getattr(w_module, space.wrap("__name__"))
-    modulename = space.str_w(w_modulename)
+    modulename = space.str0_w(w_modulename)
     if not space.is_w(check_sys_modules(space, w_modulename), w_module):
         raise operationerrfmt(
             space.w_ImportError,

pypy/module/imp/interp_imp.py

         return space.interp_w(W_File, w_file).stream
 
 def find_module(space, w_name, w_path=None):
-    name = space.str_w(w_name)
+    name = space.str0_w(w_name)
     if space.is_w(w_path, space.w_None):
         w_path = None
 
 def load_module(space, w_name, w_file, w_filename, w_info):
     w_suffix, w_filemode, w_modtype = space.unpackiterable(w_info)
 
-    filename = space.str_w(w_filename)
+    filename = space.str0_w(w_filename)
     filemode = space.str_w(w_filemode)
     if space.is_w(w_file, space.w_None):
         stream = None
         space, w_name, find_info, reuse=True)
 
 def load_source(space, w_modulename, w_filename, w_file=None):
-    filename = space.str_w(w_filename)
+    filename = space.str0_w(w_filename)
 
     stream = get_file(space, w_file, filename, 'U')
 
         stream.close()
     return w_mod
 
-@unwrap_spec(filename=str)
+@unwrap_spec(filename='str0')
 def _run_compiled_module(space, w_modulename, filename, w_file, w_module):
     # the function 'imp._run_compiled_module' is a pypy-only extension
     stream = get_file(space, w_file, filename, 'rb')
     if space.is_w(w_file, space.w_None):
         stream.close()
 
-@unwrap_spec(filename=str)
+@unwrap_spec(filename='str0')
 def load_compiled(space, w_modulename, filename, w_file=None):
     w_mod = space.wrap(Module(space, w_modulename))
     importing._prepare_module(space, w_mod, filename, None)
     return space.wrap(Module(space, w_name, add_package=False))
 
 def init_builtin(space, w_name):
-    name = space.str_w(w_name)
+    name = space.str0_w(w_name)
     if name not in space.builtin_modules:
         return
     if space.finditem(space.sys.get('modules'), w_name) is not None:
     return None
 
 def is_builtin(space, w_name):
-    name = space.str_w(w_name)
+    name = space.str0_w(w_name)
     if name not in space.builtin_modules:
         return space.wrap(0)
     if space.finditem(space.sys.get('modules'), w_name) is not None:

pypy/module/posix/interp_posix.py

     if space.isinstance_w(w_obj, space.w_unicode):
         w_obj = space.call_method(w_obj, 'encode',
                                   getfilesystemencoding(space))
-    return space.str_w(w_obj)
+    return space.str0_w(w_obj)
 
 class FileEncoder(object):
     def __init__(self, space, w_obj):
         self.w_obj = w_obj
 
     def as_bytes(self):
-        return self.space.str_w(self.w_obj)
+        return self.space.str0_w(self.w_obj)
 
     def as_unicode(self):
         space = self.space
             fname = FileEncoder(space, w_fname)
             return func(fname, *args)
         else:
-            fname = space.str_w(w_fname)
+            fname = space.str0_w(w_fname)
             return func(fname, *args)
     return dispatch
 
             fullpath = rposix._getfullpathname(path)
             w_fullpath = space.wrap(fullpath)
         else:
-            path = space.str_w(w_path)
+            path = space.str0_w(w_path)
             fullpath = rposix._getfullpathname(path)
             w_fullpath = space.wrap(fullpath)
     except OSError, e:
                 for s in result
             ]
         else:
-            dirname = space.str_w(w_dirname)
+            dirname = space.str0_w(w_dirname)
             result = rposix.listdir(dirname)
             result_w = [space.wrap(s) for s in result]
     except OSError, e:
     import signal
     os.kill(os.getpid(), signal.SIGABRT)
 
-@unwrap_spec(src=str, dst=str)
+@unwrap_spec(src='str0', dst='str0')
 def link(space, src, dst):
     "Create a hard link to a file."
     try:
     except OSError, e:
         raise wrap_oserror(space, e)
 
-@unwrap_spec(path=str)
+@unwrap_spec(path='str0')
 def readlink(space, path):
     "Return a string representing the path to which the symbolic link points."
     try:
     w_keys = space.call_method(w_env, 'keys')
     for w_key in space.unpackiterable(w_keys):
         w_value = space.getitem(w_env, w_key)
-        env[space.str_w(w_key)] = space.str_w(w_value)
+        env[space.str0_w(w_key)] = space.str0_w(w_value)
     return env
 
 def execve(space, w_command, w_args, w_env):
     except OSError, e:
         raise wrap_oserror(space, e)
 
-@unwrap_spec(path=str, uid=c_uid_t, gid=c_gid_t)
+@unwrap_spec(path='str0', uid=c_uid_t, gid=c_gid_t)
 def chown(space, path, uid, gid):
     check_uid_range(space, uid)
     check_uid_range(space, gid)
         raise wrap_oserror(space, e, path)
     return space.w_None
 
-@unwrap_spec(path=str, uid=c_uid_t, gid=c_gid_t)
+@unwrap_spec(path='str0', uid=c_uid_t, gid=c_gid_t)
 def lchown(space, path, uid, gid):
     check_uid_range(space, uid)
     check_uid_range(space, gid)

pypy/module/sys/state.py

     #
     return importlist
 
-@unwrap_spec(srcdir=str)
+@unwrap_spec(srcdir='str0')
 def pypy_initial_path(space, srcdir):
     try:
         path = getinitialpath(get(space), srcdir)

pypy/module/zipimport/interp_zipimport.py

         space = self.space
         return space.wrap(self.filename)
 
-@unwrap_spec(name=str)
+@unwrap_spec(name='str0')
 def descr_new_zipimporter(space, w_type, name):
     w = space.wrap
     ok = False

pypy/rlib/rstring.py

         assert p.const is None
         return SomeUnicodeBuilder(can_be_None=True)
 
+#___________________________________________________________________
+# Support functions for SomeString.no_NUL
+
+def assert_str0(fname):
+    assert '\x00' not in fname, "NUL byte in string"
+    return fname
+
+class Entry(ExtRegistryEntry):
+    _about_ = assert_str0
+
+    def compute_result_annotation(self, s_obj):
+        assert isinstance(s_obj, (SomeString, SomeUnicodeString))
+        if s_obj.no_NUL:
+            return s_obj
+        new_s_obj = SomeObject.__new__(s_obj.__class__)
+        new_s_obj.__dict__ = s_obj.__dict__.copy()
+        new_s_obj.no_NUL = True
+        return new_s_obj
+
+    def specialize_call(self, hop):
+        hop.exception_cannot_occur()
+        return hop.inputarg(hop.args_r[0], arg=0)
+
+def check_str0(fname):
+    """A 'probe' to trigger a failure at translation time, if the
+    string was not proved to not contain NUL characters."""
+    assert '\x00' not in fname, "NUL byte in string"
+
+class Entry(ExtRegistryEntry):
+    _about_ = check_str0
+
+    def compute_result_annotation(self, s_obj):
+        if not isinstance(s_obj, SomeString):
+            return s_obj
+        if not s_obj.no_NUL:
+            raise ValueError("Value is not no_NUL")
+
+    def specialize_call(self, hop):
+        pass
+

pypy/rpython/extfuncregistry.py

 # llinterpreter
 
 path_functions = [
-    ('join',     [str, str], str),
+    ('join',     [ll_os.str0, ll_os.str0], ll_os.str0),
+    ('dirname',  [ll_os.str0], ll_os.str0),
     ]
 
 for name, args, res in path_functions:

pypy/rpython/lltypesystem/rffi.py

 from pypy.translator.tool.cbuild import ExternalCompilationInfo
 from pypy.rpython.annlowlevel import llhelper
 from pypy.rlib.objectmodel import we_are_translated
-from pypy.rlib.rstring import StringBuilder, UnicodeBuilder
+from pypy.rlib.rstring import StringBuilder, UnicodeBuilder, assert_str0
 from pypy.rlib import jit
 from pypy.rpython.lltypesystem import llmemory
 import os, sys
         while cp[i] != lastchar:
             b.append(cp[i])
             i += 1
-        return b.build()
+        return assert_str0(b.build())
 
     # str -> char*
     # Can't inline this because of the raw address manipulation.
         while i < maxlen and cp[i] != lastchar:
             b.append(cp[i])
             i += 1
-        return b.build()
+        return assert_str0(b.build())
 
     # char* and size -> str (which can contain null bytes)
     def charpsize2str(cp, size):
         array[i] = str2charp(l[i])
     array[len(l)] = lltype.nullptr(CCHARP.TO)
     return array
+liststr2charpp._annenforceargs_ = [[annmodel.s_Str0]]  # List of strings
 
 def free_charpp(ref):
     """ frees list of char**, NULL terminated

pypy/rpython/module/ll_os.py

 from pypy.rlib import rgc
 from pypy.rlib.objectmodel import specialize
 
+str0 = SomeString()
+str0.no_NUL = True
+
+unicode0 = SomeUnicodeString()
+unicode0.no_NUL = True
+
 def monkeypatch_rposix(posixfunc, unicodefunc, signature):
     func_name = posixfunc.__name__
 
 
 class StringTraits:
     str = str
+    str0 = str0
     CHAR = rffi.CHAR
     CCHARP = rffi.CCHARP
     charp2str = staticmethod(rffi.charp2str)
 
 class UnicodeTraits:
     str = unicode
+    str0 = unicode0
     CHAR = rffi.WCHAR_T
     CCHARP = rffi.CWCHARP
     charp2str = staticmethod(rffi.wcharp2unicode)
             rffi.free_charpp(l_args)
             raise OSError(rposix.get_errno(), "execv failed")
 
-        return extdef([str, [str]], s_ImpossibleValue, llimpl=execv_llimpl,
+        return extdef([str0, [str0]], s_ImpossibleValue, llimpl=execv_llimpl,
                       export_name="ll_os.ll_os_execv")
 
 
             # appropriate
             envstrs = []
             for item in env.iteritems():
-                envstrs.append("%s=%s" % item)
+                envstr = "%s=%s" % item
+                envstrs.append(envstr)
 
             l_args = rffi.liststr2charpp(args)
             l_env = rffi.liststr2charpp(envstrs)
             raise OSError(rposix.get_errno(), "execve failed")
 
         return extdef(
-            [str, [str], {str: str}],
+            [str0, [str0], {str0: str0}],
             s_ImpossibleValue,
             llimpl=execve_llimpl,
             export_name="ll_os.ll_os_execve")
                 raise OSError(rposix.get_errno(), "os_spawnv failed")
             return rffi.cast(lltype.Signed, childpid)
 
-        return extdef([int, str, [str]], int, llimpl=spawnv_llimpl,
+        return extdef([int, str0, [str0]], int, llimpl=spawnv_llimpl,
                       export_name="ll_os.ll_os_spawnv")
 
     @registering_if(os, 'spawnve')
                 raise OSError(rposix.get_errno(), "os_spawnve failed")
             return rffi.cast(lltype.Signed, childpid)
 
-        return extdef([int, str, [str], {str: str}], int,
+        return extdef([int, str0, [str0], {str0: str0}], int,
                       llimpl=spawnve_llimpl,
                       export_name="ll_os.ll_os_spawnve")
 
         def os_open_oofakeimpl(path, flags, mode):
             return os.open(OOSupport.from_rstr(path), flags, mode)
 
-        return extdef([traits.str, int, int], int, traits.ll_os_name('open'),
+        return extdef([str0, int, int], int, traits.ll_os_name('open'),
                       llimpl=os_open_llimpl, oofakeimpl=os_open_oofakeimpl)
 
     @registering_if(os, 'getloadavg')
                     raise OSError(error, "os_readdir failed")
                 return result
 
-        return extdef([traits.str],  # a single argument which is a str
-                      [traits.str],  # returns a list of strings
+        return extdef([traits.str0],  # a single argument which is a str
+                      [traits.str0],  # returns a list of strings
                       traits.ll_os_name('listdir'),
                       llimpl=os_listdir_llimpl)
 
             if res == -1:
                 raise OSError(rposix.get_errno(), "os_chown failed")
 
-        return extdef([str, int, int], None, "ll_os.ll_os_chown",
+        return extdef([str0, int, int], None, "ll_os.ll_os_chown",
                       llimpl=os_chown_llimpl)
 
     @registering_if(os, 'lchown')
             if res == -1:
                 raise OSError(rposix.get_errno(), "os_lchown failed")
 
-        return extdef([str, int, int], None, "ll_os.ll_os_lchown",
+        return extdef([str0, int, int], None, "ll_os.ll_os_lchown",
                       llimpl=os_lchown_llimpl)
 
     @registering_if(os, 'readlink')
                     lltype.free(buf, flavor='raw')
                     bufsize *= 4
             # convert the result to a string
-            l = [buf[i] for i in range(res)]
-            result = ''.join(l)
+            result = rffi.charp2strn(buf, res)
             lltype.free(buf, flavor='raw')
             return result
 
-        return extdef([str], str,
+        return extdef([str0], str0,
                       "ll_os.ll_os_readlink",
                       llimpl=os_readlink_llimpl)
 
                 if res < 0:
                     raise OSError(rposix.get_errno(), "os_mkdir failed")
 
-        return extdef([traits.str, int], s_None, llimpl=os_mkdir_llimpl,
+        return extdef([traits.str0, int], s_None, llimpl=os_mkdir_llimpl,
                       export_name=traits.ll_os_name('mkdir'))
 
     @registering_str_unicode(os.rmdir)
             from pypy.rpython.module.ll_win32file import make_chmod_impl
             chmod_llimpl = make_chmod_impl(traits)
 
-        return extdef([traits.str, int], s_None, llimpl=chmod_llimpl,
+        return extdef([traits.str0, int], s_None, llimpl=chmod_llimpl,
                       export_name=traits.ll_os_name('chmod'))
 
     @registering_str_unicode(os.rename)
                 if not win32traits.MoveFile(oldpath, newpath):
                     raise rwin32.lastWindowsError()
 
-        return extdef([traits.str, traits.str], s_None, llimpl=rename_llimpl,
+        return extdef([traits.str0, traits.str0], s_None, llimpl=rename_llimpl,
                       export_name=traits.ll_os_name('rename'))
 
     @registering_str_unicode(getattr(os, 'mkfifo', None))
             if res < 0:
                 raise OSError(rposix.get_errno(), "os_mkfifo failed")
 
-        return extdef([traits.str, int], s_None, llimpl=mkfifo_llimpl,
+        return extdef([traits.str0, int], s_None, llimpl=mkfifo_llimpl,
                       export_name=traits.ll_os_name('mkfifo'))
 
     @registering_str_unicode(getattr(os, 'mknod', None))
             if res < 0:
                 raise OSError(rposix.get_errno(), "os_mknod failed")
 
-        return extdef([traits.str, int, int], s_None, llimpl=mknod_llimpl,
+        return extdef([traits.str0, int, int], s_None, llimpl=mknod_llimpl,
                       export_name=traits.ll_os_name('mknod'))
 
     @registering(os.umask)
             if res < 0:
                 raise OSError(rposix.get_errno(), "os_link failed")
 
-        return extdef([str, str], s_None, llimpl=link_llimpl,
+        return extdef([str0, str0], s_None, llimpl=link_llimpl,
                       export_name="ll_os.ll_os_link")
 
     @registering_if(os, 'symlink')
             if res < 0:
                 raise OSError(rposix.get_errno(), "os_symlink failed")
 
-        return extdef([str, str], s_None, llimpl=symlink_llimpl,
+        return extdef([str0, str0], s_None, llimpl=symlink_llimpl,
                       export_name="ll_os.ll_os_symlink")
 
     @registering_if(os, 'fork')

pypy/rpython/module/ll_os_stat.py

 def register_stat_variant(name, traits):
     if name != 'fstat':
         arg_is_path = True
-        s_arg = traits.str
+        s_arg = traits.str0
         ARG1 = traits.CCHARP
     else:
         arg_is_path = False
             [s_arg], s_StatResult, traits.ll_os_name(name),
             llimpl=posix_stat_llimpl)
 
-    assert traits.str is str
-
     if sys.platform.startswith('linux'):
         # because we always use _FILE_OFFSET_BITS 64 - this helps things work that are not a c compiler
         _functions = {'stat':  'stat64',