कारण eval
और exec
इतने खतरनाक हैं कि डिफ़ॉल्ट compile
फ़ंक्शन किसी भी मान्य अजगर अभिव्यक्ति के लिए बायटेकोड उत्पन्न करेगा, और डिफ़ॉल्ट eval
या exec
किसी भी मान्य अजगर बाइटकोड को निष्पादित करेगा। तिथि के सभी उत्तरों ने बायटेकोड को प्रतिबंधित करने पर ध्यान केंद्रित किया है जो उत्पन्न हो सकता है (इनपुट को सैनिटाइज़ करके) या एएसटी का उपयोग करके अपने स्वयं के डोमेन-विशिष्ट-भाषा का निर्माण कर सकता है।
इसके बजाय, आप आसानी से एक सरल eval
फ़ंक्शन बना सकते हैं जो कुछ भी नापाक करने में असमर्थ है और आसानी से स्मृति या उपयोग किए गए समय पर रनटाइम चेक कर सकते हैं। बेशक, अगर यह सरल गणित है, की तुलना में एक शॉर्टकट है।
c = compile(stringExp, 'userinput', 'eval')
if c.co_code[0]==b'd' and c.co_code[3]==b'S':
return c.co_consts[ord(c.co_code[1])+ord(c.co_code[2])*256]
जिस तरह से यह कार्य सरल है, किसी भी स्थिर गणितीय अभिव्यक्ति का संकलन के दौरान सुरक्षित रूप से मूल्यांकन किया जाता है और एक स्थिर के रूप में संग्रहीत किया जाता है। संकलित करके लौटाया गया कोड ऑब्जेक्ट होता है d
, जिसके लिए बाइटकोड होता है LOAD_CONST
, उसके बाद लोड करने के लिए निरंतर की संख्या (आमतौर पर सूची में अंतिम), जिसके बाद S
बायटोड होता है RETURN_VALUE
। यदि यह शॉर्टकट काम नहीं करता है, तो इसका मतलब है कि उपयोगकर्ता इनपुट एक स्थिर अभिव्यक्ति नहीं है (जिसमें एक चर या फ़ंक्शन कॉल या समान है)।
यह कुछ और परिष्कृत इनपुट प्रारूपों के लिए भी द्वार खोलता है। उदाहरण के लिए:
stringExp = "1 + cos(2)"
इसके लिए वास्तव में बाइटकोड का मूल्यांकन करना आवश्यक है, जो अभी भी काफी सरल है। पायथन बाइटकोड एक स्टैक ओरिएंटेड भाषा है, इसलिए सब कुछ एक TOS=stack.pop(); op(TOS); stack.put(TOS)
समान या समान का मामला है । कुंजी केवल उन ऑपकोड को लागू करना है जो सुरक्षित हैं (लोडिंग / स्टोरिंग मान, गणित संचालन, रिटर्न मान) और असुरक्षित नहीं हैं (विशेषता लुकअप)। यदि आप चाहते हैं कि उपयोगकर्ता फ़ंक्शंस को कॉल करने में सक्षम हो (ऊपर दिए गए शॉर्टकट का उपयोग न करने का पूरा कारण), तो CALL_FUNCTION
केवल 'सुरक्षित' सूची में केवल फ़ंक्शंस के अपने कार्यान्वयन को सरल बनाएं ।
from dis import opmap
from Queue import LifoQueue
from math import sin,cos
import operator
globs = {'sin':sin, 'cos':cos}
safe = globs.values()
stack = LifoQueue()
class BINARY(object):
def __init__(self, operator):
self.op=operator
def __call__(self, context):
stack.put(self.op(stack.get(),stack.get()))
class UNARY(object):
def __init__(self, operator):
self.op=operator
def __call__(self, context):
stack.put(self.op(stack.get()))
def CALL_FUNCTION(context, arg):
argc = arg[0]+arg[1]*256
args = [stack.get() for i in range(argc)]
func = stack.get()
if func not in safe:
raise TypeError("Function %r now allowed"%func)
stack.put(func(*args))
def LOAD_CONST(context, arg):
cons = arg[0]+arg[1]*256
stack.put(context['code'].co_consts[cons])
def LOAD_NAME(context, arg):
name_num = arg[0]+arg[1]*256
name = context['code'].co_names[name_num]
if name in context['locals']:
stack.put(context['locals'][name])
else:
stack.put(context['globals'][name])
def RETURN_VALUE(context):
return stack.get()
opfuncs = {
opmap['BINARY_ADD']: BINARY(operator.add),
opmap['UNARY_INVERT']: UNARY(operator.invert),
opmap['CALL_FUNCTION']: CALL_FUNCTION,
opmap['LOAD_CONST']: LOAD_CONST,
opmap['LOAD_NAME']: LOAD_NAME
opmap['RETURN_VALUE']: RETURN_VALUE,
}
def VMeval(c):
context = dict(locals={}, globals=globs, code=c)
bci = iter(c.co_code)
for bytecode in bci:
func = opfuncs[ord(bytecode)]
if func.func_code.co_argcount==1:
ret = func(context)
else:
args = ord(bci.next()), ord(bci.next())
ret = func(context, args)
if ret:
return ret
def evaluate(expr):
return VMeval(compile(expr, 'userinput', 'eval'))
जाहिर है, इसका वास्तविक संस्करण थोड़ा लंबा होगा (इसमें 119 ऑपकोड हैं, जिनमें से 24 गणित से संबंधित हैं)। जोड़ना STORE_FAST
और एक जोड़े को दूसरों की तरह इनपुट के लिए अनुमति देगा 'x=5;return x+x
, तुच्छ आसानी से। इसका उपयोग उपयोगकर्ता द्वारा बनाए गए कार्यों को निष्पादित करने के लिए भी किया जा सकता है, इसलिए जब तक उपयोगकर्ता द्वारा बनाए गए कार्य स्वयं VMeval के माध्यम से निष्पादित नहीं होते हैं (उन्हें कॉल करने योग्य नहीं बनाते हैं - या वे कॉलबैक के रूप में उपयोग किया जा सकता है)। हैंडलिंग लूप को goto
बाइटकोड के लिए समर्थन की आवश्यकता होती है, जिसका अर्थ है कि सबसे स्पष्ट होने से बदलना )।for
पुनरावृत्तिwhile
और एक संकेतक को वर्तमान निर्देश को बनाए रखना, लेकिन यह अधिक कठिन नहीं है। डॉस के प्रतिरोध के लिए, मुख्य लूप को यह जांचना चाहिए कि गणना शुरू होने के बाद कितना समय बीत चुका है, और कुछ ऑपरेटरों को कुछ उचित सीमा से अधिक इनपुट से इनकार करना चाहिए (BINARY_POWER
हालांकि यह दृष्टिकोण सरल अभिव्यक्तियों के लिए एक साधारण व्याकरण पार्सर की तुलना में कुछ अधिक लंबा है (ऊपर संकलित स्थिरांक को हथियाने के बारे में ऊपर देखें), यह आसानी से अधिक जटिल इनपुट तक फैलता है, और व्याकरण से निपटने की आवश्यकता नहीं है ( compile
मनमाने ढंग से जटिल कुछ भी ले लो और इसे कम कर देता है) सरल निर्देशों का एक क्रम)।