SVF
MemModel.cpp
Go to the documentation of this file.
1 //===- MemModel.cpp -- Memory model for pointer analysis----------------------//
2 //
3 // SVF: Static Value-Flow Analysis
4 //
5 // Copyright (C) <2013-2017> <Yulei Sui>
6 //
7 
8 // This program is free software: you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation, either version 3 of the License, or
11 // (at your option) any later version.
12 
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 // GNU General Public License for more details.
17 
18 // You should have received a copy of the GNU General Public License
19 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 //
21 //===----------------------------------------------------------------------===//
22 
23 /*
24  * MemModel.cpp
25  *
26  * Created on: Oct 11, 2013
27  * Author: Yulei Sui
28  */
29 
30 #include "SVF-FE/SymbolTableInfo.h"
31 #include "MemoryModel/MemModel.h"
32 #include "Util/SVFModule.h"
33 #include "Util/SVFUtil.h"
34 #include "SVF-FE/LLVMUtil.h"
35 #include "SVF-FE/CPPUtil.h"
37 #include "SVF-FE/GEPTypeBridgeIterator.h" // include bridge_gep_iterator
38 
39 using namespace std;
40 using namespace SVF;
41 using namespace SVFUtil;
42 
43 u32_t StInfo::maxFieldLimit = 0;
44 
48 void ObjTypeInfo::analyzeGlobalStackObjType(const Value* val)
49 {
50 
51  const PointerType * refty = SVFUtil::dyn_cast<PointerType>(val->getType());
52  assert(SVFUtil::isa<PointerType>(refty) && "this value should be a pointer type!");
53  Type* elemTy = refty->getElementType();
54  bool isPtrObj = false;
55  // Find the inter nested array element
56  while (const ArrayType *AT= SVFUtil::dyn_cast<ArrayType>(elemTy))
57  {
58  elemTy = AT->getElementType();
59  if(elemTy->isPointerTy())
60  isPtrObj = true;
61  if(SVFUtil::isa<GlobalVariable>(val) && SVFUtil::cast<GlobalVariable>(val)->hasInitializer()
62  && SVFUtil::isa<ConstantArray>(SVFUtil::cast<GlobalVariable>(val)->getInitializer()))
63  {
64  setFlag(CONST_ARRAY_OBJ);
65  }
66  else
67  setFlag(VAR_ARRAY_OBJ);
68  }
69  if (const StructType *ST= SVFUtil::dyn_cast<StructType>(elemTy))
70  {
71  const std::vector<FieldInfo>& flattenFields = SymbolTableInfo::SymbolInfo()->getFlattenFieldInfoVec(ST);
72  for(std::vector<FieldInfo>::const_iterator it = flattenFields.begin(), eit = flattenFields.end();
73  it!=eit; ++it)
74  {
75  if((*it).getFlattenElemTy()->isPointerTy())
76  isPtrObj = true;
77  }
78  if(SVFUtil::isa<GlobalVariable>(val) && SVFUtil::cast<GlobalVariable>(val)->hasInitializer()
79  && SVFUtil::isa<ConstantStruct>(SVFUtil::cast<GlobalVariable>(val)->getInitializer()))
80  setFlag(CONST_STRUCT_OBJ);
81  else
82  setFlag(VAR_STRUCT_OBJ);
83  }
84  else if (elemTy->isPointerTy())
85  {
86  isPtrObj = true;
87  }
88 
89  if(isPtrObj)
90  setFlag(HASPTR_OBJ);
91 }
92 
96 void ObjTypeInfo::analyzeHeapStaticObjType(const Value*)
97 {
98  // TODO: Heap and static objects are considered as pointers right now.
99  // Refine this function to get more details about heap and static objects.
100  setFlag(HASPTR_OBJ);
101 }
102 
106 u32_t ObjTypeInfo::getObjSize(const Value* val)
107 {
108 
109  Type* ety = SVFUtil::cast<PointerType>(val->getType())->getElementType();
110  u32_t numOfFields = 1;
111  if (SVFUtil::isa<StructType>(ety) || SVFUtil::isa<ArrayType>(ety))
112  {
113  numOfFields = SymbolTableInfo::SymbolInfo()->getFlattenFieldInfoVec(ety).size();
114  }
115  return numOfFields;
116 }
117 
121 void ObjTypeInfo::init(const Value* val)
122 {
123 
124  Size_t objSize = 1;
125  // Global variable
126  if (SVFUtil::isa<Function>(val))
127  {
128  setFlag(FUNCTION_OBJ);
129  analyzeGlobalStackObjType(val);
130  objSize = getObjSize(val);
131  }
132  else if(SVFUtil::isa<AllocaInst>(val))
133  {
134  setFlag(STACK_OBJ);
135  analyzeGlobalStackObjType(val);
136  objSize = getObjSize(val);
137  }
138  else if(SVFUtil::isa<GlobalVariable>(val))
139  {
140  setFlag(GLOBVAR_OBJ);
141  if(SymbolTableInfo::SymbolInfo()->isConstantObjSym(val))
142  setFlag(CONST_OBJ);
143  analyzeGlobalStackObjType(val);
144  objSize = getObjSize(val);
145  }
146  else if (SVFUtil::isa<Instruction>(val) && isHeapAllocExtCall(SVFUtil::cast<Instruction>(val)))
147  {
148  setFlag(HEAP_OBJ);
149  analyzeHeapStaticObjType(val);
150  // Heap object, label its field as infinite here
151  objSize = -1;
152  }
153  else if (SVFUtil::isa<Instruction>(val) && isStaticExtCall(SVFUtil::cast<Instruction>(val)))
154  {
155  setFlag(STATIC_OBJ);
156  analyzeHeapStaticObjType(val);
157  // static object allocated before main, label its field as infinite here
158  objSize = -1;
159  }
160  else if(ArgInProgEntryFunction(val))
161  {
162  setFlag(STATIC_OBJ);
163  analyzeHeapStaticObjType(val);
164  // user input data, label its field as infinite here
165  objSize = -1;
166  }
167  else
168  assert("what other object do we have??");
169 
170  // Reset maxOffsetLimit if it is over the total fieldNum of this object
171  if(objSize > 0 && maxOffsetLimit > objSize)
172  maxOffsetLimit = objSize;
173 
174 }
175 
179 bool ObjTypeInfo::isNonPtrFieldObj(const LocationSet& ls)
180 {
181  if (isHeap() || isStaticObj())
182  return false;
183 
184  const Type* ety = getType();
185  while (const ArrayType *AT= SVFUtil::dyn_cast<ArrayType>(ety))
186  {
187  ety = AT->getElementType();
188  }
189 
190  if (SVFUtil::isa<StructType>(ety) || SVFUtil::isa<ArrayType>(ety))
191  {
192  bool hasIntersection = false;
193  const vector<FieldInfo> &infovec = SymbolTableInfo::SymbolInfo()->getFlattenFieldInfoVec(ety);
194  vector<FieldInfo>::const_iterator it = infovec.begin();
195  vector<FieldInfo>::const_iterator eit = infovec.end();
196  for (; it != eit; ++it)
197  {
198  const FieldInfo& fieldLS = *it;
199  if (ls.intersects(LocationSet(fieldLS)))
200  {
201  hasIntersection = true;
202  if (fieldLS.getFlattenElemTy()->isPointerTy())
203  return false;
204  }
205  }
206  assert(hasIntersection && "cannot find field of specified offset");
207  return true;
208  }
209  else
210  {
211  if (isStaticObj() || isHeap())
212  {
213  // TODO: Objects which cannot find proper field for a certain offset including
214  // arguments in main(), static objects allocated before main and heap
215  // objects. Right now they're considered to have infinite fields and we
216  // treat each field as pointers conservatively.
217  // Try to model static and heap objects more accurately in the future.
218  return false;
219  }
220  else
221  {
222  // TODO: Using new memory model (locMM) may create objects with spurious offset
223  // as we simply return new offset by mod operation without checking its
224  // correctness in LocSymTableInfo::getModulusOffset(). So the following
225  // assertion may fail. Try to refine the new memory model.
226  //assert(ls.getOffset() == 0 && "cannot get a field from a non-struct type");
227  return (hasPtrObj() == false);
228  }
229  }
230 }
231 
232 
236 void MemObj::setFieldSensitive()
237 {
238  typeInfo->setMaxFieldOffsetLimit(StInfo::getMaxFieldLimit());
239 }
240 /*
241  * Initial the memory object here
242  */
243 void MemObj::init(const Type* type)
244 {
245  typeInfo = new ObjTypeInfo(StInfo::getMaxFieldLimit(),type);
246  typeInfo->setFlag(ObjTypeInfo::HEAP_OBJ);
247  typeInfo->setFlag(ObjTypeInfo::HASPTR_OBJ);
248 }
249 
253 MemObj::MemObj(const Value *val, SymID id) :
254  refVal(val), GSymID(id), typeInfo(nullptr)
255 {
256  init(val);
257 }
258 
262 MemObj::MemObj(SymID id, const Type* type) :
263  refVal(nullptr), GSymID(id), typeInfo(nullptr)
264 {
265  init(type);
266 }
267 
272 {
274 }
275 
276 
278 const Type* MemObj::getType() const
279 {
280  if (isHeap() == false)
281  {
282  if(const PointerType* type = SVFUtil::dyn_cast<PointerType>(typeInfo->getType()))
283  return type->getElementType();
284  else
285  return typeInfo->getType();
286  }
287  else if (refVal && SVFUtil::isa<Instruction>(refVal))
288  return SVFUtil::getTypeOfHeapAlloc(SVFUtil::cast<Instruction>(refVal));
289  else
290  return typeInfo->getType();
291 }
292 /*
293  * Destroy the fields of the memory object
294  */
296 {
297  delete typeInfo;
298  typeInfo = nullptr;
299 }
300 
301 
306 {
307 
308  assert(V);
309  int baseIndex = -1;
310  int index = 0;
311  for (bridge_gep_iterator gi = bridge_gep_begin(*V), ge = bridge_gep_end(*V);
312  gi != ge; ++gi, ++index)
313  {
314  if(SVFUtil::isa<ConstantInt>(gi.getOperand()) == false)
315  baseIndex = index;
316  }
317 
318  index = 0;
319  for (bridge_gep_iterator gi = bridge_gep_begin(*V), ge = bridge_gep_end(*V);
320  gi != ge; ++gi, ++index)
321  {
322 
323  if (index <= baseIndex)
324  {
326  // Handling pointer types
327  if (const PointerType* pty = SVFUtil::dyn_cast<PointerType>(*gi))
328  {
329  const Type* et = pty->getElementType();
330  Size_t sz = getTypeSizeInBytes(et);
331 
332  Size_t num = 1;
333  if (const ArrayType* aty = SVFUtil::dyn_cast<ArrayType>(et))
334  num = aty->getNumElements();
335  else
336  num = StInfo::getMaxFieldLimit();
337 
338  ls.addElemNumStridePair(std::make_pair(num, sz));
339  }
340  // Calculate the size of the array element
341  else if(const ArrayType* at = SVFUtil::dyn_cast<ArrayType>(*gi))
342  {
343  const Type* et = at->getElementType();
344  Size_t sz = getTypeSizeInBytes(et);
345  Size_t num = at->getNumElements();
346  ls.addElemNumStridePair(std::make_pair(num, sz));
347  }
348  else
349  assert(false && "what other types?");
350  }
351  // constant offset
352  else
353  {
354  assert(SVFUtil::isa<ConstantInt>(gi.getOperand()) && "expecting a constant");
355 
356  ConstantInt *op = SVFUtil::cast<ConstantInt>(gi.getOperand());
357 
358  //The actual index
359  Size_t idx = op->getSExtValue();
360 
361  // Handling pointer types
362  // These GEP instructions are simply making address computations from the base pointer address
363  // e.g. idx1 = (char*) &MyVar + 4, at this case gep only one offset index (idx)
364  if (const PointerType* pty = SVFUtil::dyn_cast<PointerType>(*gi))
365  {
366  const Type* et = pty->getElementType();
367  Size_t sz = getTypeSizeInBytes(et);
368  ls.setByteOffset(ls.getByteOffset() + idx * sz);
369  }
370  // Calculate the size of the array element
371  else if(const ArrayType* at = SVFUtil::dyn_cast<ArrayType>(*gi))
372  {
373  const Type* et = at->getElementType();
374  Size_t sz = getTypeSizeInBytes(et);
375  ls.setByteOffset(ls.getByteOffset() + idx * sz);
376  }
377  // Handling struct here
378  else if (const StructType *ST = SVFUtil::dyn_cast<StructType>(*gi))
379  {
380  assert(op && "non-const struct index in GEP");
381  const vector<u32_t> &so = SymbolTableInfo::SymbolInfo()->getFattenFieldOffsetVec(ST);
382  if ((unsigned)idx >= so.size())
383  {
384  outs() << "!! Struct index out of bounds" << idx << "\n";
385  assert(0);
386  }
387  //add the translated offset
388  ls.setByteOffset(ls.getByteOffset() + so[idx]);
389  }
390  else
391  assert(false && "what other types?");
392  }
393  }
394  return true;
395 }
396 
401 {
402  /*
403  StInfo *stinfo = new StInfo();
404  typeToFieldInfo[ty] = stinfo;
405 
409  u64_t out_num = ty->getNumElements();
410  const Type* elemTy = ty->getElementType();
411  u32_t out_stride = getTypeSizeInBytes(elemTy);
412 
414  stinfo->addOffsetWithType(0, elemTy);
415 
416  while (const ArrayType* aty = dyn_cast<ArrayType>(elemTy)) {
417  out_num *= aty->getNumElements();
418  elemTy = aty->getElementType();
419  out_stride = getTypeSizeInBytes(elemTy);
420  }
421 
425  StInfo* elemStInfo = getStructInfo(elemTy);
426  u32_t nfE = elemStInfo->getFlattenFieldInfoVec().size();
427  for (u32_t j = 0; j < nfE; j++) {
428  u32_t off = elemStInfo->getFlattenFieldInfoVec()[j].getFlattenOffset();
429  const Type* fieldTy = elemStInfo->getFlattenFieldInfoVec()[j].getFlattenElemTy();
430  FieldInfo::ElemNumStridePairVec pair = elemStInfo->getFlattenFieldInfoVec()[j].getElemNumStridePairVect();
432  pair.push_back(std::make_pair(out_num, out_stride));
433  FieldInfo field(off, fieldTy, pair);
434  stinfo->getFlattenFieldInfoVec().push_back(field);
435  }
436  */
437 }
438 
439 
440 /*
441  * Recursively collect the memory layout information for a struct type
442  */
444 {
445  /*
446  StInfo *stinfo = new StInfo();
447  typeToFieldInfo[ty] = stinfo;
448 
449  const StructLayout *stTySL = getDataLayout(getModule().getMainLLVMModule())->getStructLayout( const_cast<StructType *>(ty) );
450 
451  u32_t field_idx = 0;
452  for (StructType::element_iterator it = ty->element_begin(), ie =
453  ty->element_end(); it != ie; ++it, ++field_idx) {
454  const Type *et = *it;
455 
456  // The offset is where this element will be placed in the struct.
457  // This offset is computed after alignment with the current struct
458  u64_t eOffsetInBytes = stTySL->getElementOffset(field_idx);
459 
460  //The offset is where this element will be placed in the exp. struct.
463  stinfo->addOffsetWithType(static_cast<u32_t>(eOffsetInBytes), et);
464 
465  StInfo* fieldStinfo = getStructInfo(et);
466  u32_t nfE = fieldStinfo->getFlattenFieldInfoVec().size();
467  //Copy ST's info, whose element 0 is the size of ST itself.
468  for (u32_t j = 0; j < nfE; j++) {
469  u32_t oft = eOffsetInBytes + fieldStinfo->getFlattenFieldInfoVec()[j].getFlattenOffset();
470  const Type* elemTy = fieldStinfo->getFlattenFieldInfoVec()[j].getFlattenElemTy();
471  FieldInfo::ElemNumStridePairVec pair = fieldStinfo->getFlattenFieldInfoVec()[j].getElemNumStridePairVect();
472  pair.push_back(std::make_pair(1, 0));
473  FieldInfo newField(oft, elemTy, pair);
474  stinfo->getFlattenFieldInfoVec().push_back(newField);
475  }
476  }
477 
478  // verifyStructSize(stinfo,stTySL->getSizeInBytes());
479 
480  //Record the size of the complete struct and update max_struct.
481  if (stTySL->getSizeInBytes() > maxStSize) {
482  maxStruct = ty;
483  maxStSize = stTySL->getSizeInBytes();
484  }
485  */
486 }
487 
488 
494 {
495  const Type* ety = obj->getType();
496 
497  if (SVFUtil::isa<StructType>(ety) || SVFUtil::isa<ArrayType>(ety))
498  {
500  const std::vector<FieldInfo>& infovec = SymbolTableInfo::SymbolInfo()->getFlattenFieldInfoVec(ety);
501  std::vector<FieldInfo>::const_iterator it = infovec.begin();
502  std::vector<FieldInfo>::const_iterator eit = infovec.end();
503  for (; it != eit; ++it)
504  {
505  const FieldInfo& fieldLS = *it;
506  LocationSet rhsLS(fieldLS);
508  if (result == LocationSet::Same ||
509  result == LocationSet::Superset ||
510  result == LocationSet::Subset)
511  return ls;
512  else if (result == LocationSet::Overlap)
513  {
514  // TODO:
515  return ls;
516  }
517  else if (result == LocationSet::NonOverlap)
518  {
519  continue;
520  }
521 
522  assert(false && "cannot find an appropriate field for specified LocationSet");
523  return ls;
524  }
525  }
526  else
527  {
528  if (obj->isStaticObj() || obj->isHeap())
529  {
530  // TODO: Objects which cannot find proper field for a certain offset including
531  // arguments in main(), static objects allocated before main and heap
532  // objects. Right now they're considered to have infinite fields. So we
533  // just return the location set without modifying it.
534  return ls;
535  }
536  else
537  {
538  // TODO: Using new memory model (locMM) may create objects with spurious offset
539  // as we simply return new offset by mod operation without checking its
540  // correctness in LocSymTableInfo::getModulusOffset(). So the following
541  // assertion may fail. Try to refine the new memory model.
542  assert(ls.isConstantOffset() && "expecting a constant location set");
543  return ls;
544  }
545  }
546 
548  if (ls.isConstantOffset())
549  {
553  Size_t offset = ls.getOffset();
554  if(offset < 0)
555  {
556  writeWrnMsg("try to create a gep node with negative offset.");
557  offset = abs(offset);
558  }
559  u32_t maxOffset = obj->getMaxFieldOffsetLimit();
560  if (maxOffset != 0)
561  offset = offset % maxOffset;
562  else
563  offset = 0;
564  }
566  else
567  {
568 
569  }
570 
571  return ls;
572 }
573 
578 {
579 
580  u32_t lastOff = stinfo->getFlattenFieldInfoVec().back().getFlattenByteOffset();
581  u32_t strideSize = 0;
582  FieldInfo::ElemNumStridePairVec::const_iterator pit = stinfo->getFlattenFieldInfoVec().back().elemStridePairBegin();
583  FieldInfo::ElemNumStridePairVec::const_iterator epit = stinfo->getFlattenFieldInfoVec().back().elemStridePairEnd();
584  for(; pit!=epit; ++pit)
585  strideSize += pit->first * pit->second;
586 
587  u32_t lastSize = getTypeSizeInBytes(stinfo->getFlattenFieldInfoVec().back().getFlattenElemTy());
589  assert((structSize == lastOff + strideSize + lastSize) && "struct size not consistent");
590 
591 }
592 
597 {
598 
599  Type* ety = SVFUtil::cast<PointerType>(val->getType())->getElementType();
601 }
ObjTypeInfo * typeInfo
Type information of this object.
Definition: MemModel.h:293
const Type * getFlattenElemTy() const
Definition: LocationSet.h:68
bool isBlackHoleObj() const
Whether it is a black hole object.
Definition: MemModel.cpp:271
static bool isBlkObj(NodeID id)
bool intersects(const LocationSet &RHS) const
Return TRUE if we share any location in common with RHS.
Definition: LocationSet.h:225
void setByteOffset(Size_t os)
Definition: LocationSet.h:206
MemObj(const Value *val, SymID id)
Constructor.
Definition: MemModel.cpp:253
void init(const Value *val)
Initialize the object.
bool ArgInProgEntryFunction(const Value *val)
Return true if this is an argument of a program entry function (e.g. main)
Definition: LLVMUtil.h:441
llvm::Type Type
Definition: BasicTypes.h:75
Size_t getByteOffset() const
Definition: LocationSet.h:198
#define assert(ex)
Definition: util.h:141
llvm::PointerType PointerType
Definition: BasicTypes.h:108
const llvm::Type * getType() const
Get obj type.
Definition: MemModel.cpp:278
virtual LocationSet getModulusOffset(const MemObj *obj, const LocationSet &ls)
Given an offset from a Gep Instruction, return it modulus offset by considering memory layout...
Definition: MemModel.cpp:493
llvm::ArrayType ArrayType
Definition: BasicTypes.h:107
llvm::ConstantInt ConstantInt
Definition: BasicTypes.h:153
virtual void collectArrayInfo(const ArrayType *T)
Collect the array info.
Definition: MemModel.cpp:400
bool isConstantOffset() const
Return TRUE if this is a constant location set.
Definition: LocationSet.h:219
unsigned u32_t
Definition: SVFBasicTypes.h:75
void writeWrnMsg(std::string msg)
Writes a message run through wrnMsg.
Definition: SVFUtil.cpp:67
const std::vector< FieldInfo > & getFlattenFieldInfoVec(const Type *T)
void addElemNumStridePair(const NodePair &pair)
Definition: LocationSet.cpp:42
std::vector< FieldInfo > & getFlattenFieldInfoVec()
Definition: MemModel.h:111
bridge_gep_iterator bridge_gep_begin(const User *GEP)
llvm::StructType StructType
LLVM types.
Definition: BasicTypes.h:106
signed long Size_t
Definition: SVFBasicTypes.h:78
const std::vector< u32_t > & getFattenFieldOffsetVec(const Type *T)
unsigned SymID
Definition: SVFBasicTypes.h:82
const Type * getType() const
Get LLVM type.
Definition: MemModel.h:189
bool isStaticObj() const
Definition: MemModel.h:364
static u32_t getMaxFieldLimit()
Definition: MemModel.h:88
void verifyStructSize(StInfo *stInfo, u32_t structSize)
Verify struct size construction.
Definition: MemModel.cpp:577
bool isStaticExtCall(const CallSite cs)
Definition: LLVMUtil.h:194
static SymbolTableInfo * SymbolInfo()
Singleton design here to make sure we only have one instance during any analysis. ...
static LSRelation checkRelation(const LocationSet &LHS, const LocationSet &RHS)
Check relations of two location sets.
Definition: LocationSet.h:231
bool isHeapAllocExtCall(const CallSite cs)
Definition: LLVMUtil.h:116
raw_ostream & outs()
Overwrite llvm::outs()
Definition: SVFUtil.h:47
void destroy()
Clean up memory.
Definition: MemModel.cpp:295
bridge_gep_iterator bridge_gep_end(const User *GEP)
u32_t getTypeSizeInBytes(const Type *type)
Helper method to get the size of the type from target data layout.
Size_t getOffset() const
Get methods.
Definition: LocationSet.h:194
for isBitcode
Definition: ContextDDA.h:15
const Value * refVal
The unique pointer value refer this object.
Definition: MemModel.h:289
SymID GSymID
The unique id in the graph.
Definition: MemModel.h:291
virtual void collectStructInfo(const StructType *T)
Collect the struct info.
Definition: MemModel.cpp:443
LLVM_NODISCARD std::enable_if<!is_simple_type< Y >::value, typename cast_retty< X, const Y >::ret_type >::type dyn_cast(const Y &Val)
Definition: Casting.h:343
virtual bool computeGepOffset(const User *V, LocationSet &ls)
Compute gep offset.
Definition: MemModel.cpp:305
static int result
Definition: cuddGenetic.c:121
Size_t getMaxFieldOffsetLimit() const
Get max field offset limit.
Definition: MemModel.h:319
bool isHeap() const
Definition: MemModel.h:372
llvm::Value Value
Definition: BasicTypes.h:78
llvm::User User
Definition: BasicTypes.h:86
const Type * getTypeOfHeapAlloc(const llvm::Instruction *inst)
Get the type of the heap allocation.
Definition: LLVMUtil.cpp:270
u32_t getObjSize(const Value *val)
Get the size of this object.
Definition: MemModel.cpp:596