1 package org.robolectric.res.android; 2 3 // transliterated from https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/libs/androidfw/ResourceTypes.cpp 4 // and https://android.googlesource.com/platform/frameworks/base/+/android-9.0.0_r12/include/androidfw/ResourceTypes.h 5 6 import static org.robolectric.res.android.Errors.BAD_TYPE; 7 import static org.robolectric.res.android.Errors.NAME_NOT_FOUND; 8 import static org.robolectric.res.android.Errors.NO_ERROR; 9 import static org.robolectric.res.android.Errors.NO_INIT; 10 import static org.robolectric.res.android.ResourceString.decodeString; 11 import static org.robolectric.res.android.ResourceTypes.validate_chunk; 12 import static org.robolectric.res.android.Util.ALOGI; 13 import static org.robolectric.res.android.Util.ALOGW; 14 import static org.robolectric.res.android.Util.SIZEOF_INT; 15 import static org.robolectric.res.android.Util.isTruthy; 16 17 import java.lang.ref.WeakReference; 18 import java.nio.ByteBuffer; 19 import java.nio.ByteOrder; 20 import java.util.Objects; 21 import org.robolectric.res.android.ResourceString.Type; 22 import org.robolectric.res.android.ResourceTypes.ResChunk_header; 23 import org.robolectric.res.android.ResourceTypes.ResStringPool_header; 24 import org.robolectric.res.android.ResourceTypes.ResStringPool_header.Writer; 25 import org.robolectric.res.android.ResourceTypes.ResStringPool_ref; 26 import org.robolectric.res.android.ResourceTypes.ResStringPool_span; 27 import org.robolectric.res.android.ResourceTypes.WithOffset; 28 29 /** 30 * Convenience class for accessing data in a ResStringPool resource. 31 */ 32 @SuppressWarnings("NewApi") 33 public class ResStringPool { 34 35 private static boolean kDebugStringPoolNoisy = false; 36 37 private final long myNativePtr; 38 39 private int mError; 40 41 byte[] mOwnedData; 42 //private Object mOwnedData; 43 44 private ResStringPool_header mHeader; 45 private int mSize; 46 // private mutable Mutex mDecodeLock; 47 // const uint32_t* mEntries; 48 private IntArray mEntries; 49 // const uint32_t* mEntryStyles; 50 private IntArray mEntryStyles; 51 // const void* mStrings; 52 private int mStrings; 53 //private List<String> mStrings; 54 //private String[] mCache; 55 //private char16_t mutable** mCache; 56 private int mStringPoolSize; // number of uint16_t 57 // const uint32_t* mStyles; 58 private int mStyles; 59 private int mStylePoolSize; // number of int 60 ResStringPool()61 public ResStringPool() { 62 mError = NO_INIT; 63 myNativePtr = Registries.NATIVE_STRING_POOLS.register(new WeakReference<>(this)); 64 } 65 66 @Override finalize()67 protected void finalize() throws Throwable { 68 Registries.NATIVE_STRING_POOLS.unregister(myNativePtr); 69 } 70 getNativePtr()71 public long getNativePtr() { 72 return myNativePtr; 73 } 74 getNativeObject(long nativeId)75 public static ResStringPool getNativeObject(long nativeId) { 76 return Registries.NATIVE_STRING_POOLS.getNativeObject(nativeId).get(); 77 } 78 79 static class IntArray extends WithOffset { IntArray(ByteBuffer buf, int offset)80 IntArray(ByteBuffer buf, int offset) { 81 super(buf, offset); 82 } 83 get(int idx)84 int get(int idx) { 85 return myBuf().getInt(myOffset() + idx * SIZEOF_INT); 86 } 87 } 88 setToEmpty()89 void setToEmpty() 90 { 91 uninit(); 92 93 ByteBuffer buf = ByteBuffer.allocate(16 * 1024).order(ByteOrder.LITTLE_ENDIAN); 94 Writer resStringPoolWriter = new Writer(); 95 resStringPoolWriter.write(buf); 96 mOwnedData = new byte[buf.position()]; 97 buf.position(); 98 buf.get(mOwnedData); 99 100 ResStringPool_header header = new ResStringPool_header(buf, 0); 101 mSize = 0; 102 mEntries = null; 103 mStrings = 0; 104 mStringPoolSize = 0; 105 mEntryStyles = null; 106 mStyles = 0; 107 mStylePoolSize = 0; 108 mHeader = header; 109 } 110 111 // status_t setTo(const void* data, size_t size, bool copyData=false); setTo(ByteBuffer buf, int offset, int size, boolean copyData)112 public int setTo(ByteBuffer buf, int offset, int size, boolean copyData) { 113 if (!isTruthy(buf) || !isTruthy(size)) { 114 return (mError=BAD_TYPE); 115 } 116 117 uninit(); 118 119 // The chunk must be at least the size of the string pool header. 120 if (size < ResStringPool_header.SIZEOF) { 121 ALOGW("Bad string block: data size %zu is too small to be a string block", size); 122 return (mError=BAD_TYPE); 123 } 124 125 // The data is at least as big as a ResChunk_header, so we can safely validate the other 126 // header fields. 127 // `data + size` is safe because the source of `size` comes from the kernel/filesystem. 128 if (validate_chunk(new ResChunk_header(buf, offset), ResStringPool_header.SIZEOF, 129 size, 130 "ResStringPool_header") != NO_ERROR) { 131 ALOGW("Bad string block: malformed block dimensions"); 132 return (mError=BAD_TYPE); 133 } 134 135 // final boolean notDeviceEndian = htods((short) 0xf0) != 0xf0; 136 // 137 // if (copyData || notDeviceEndian) { 138 // mOwnedData = data; 139 // if (mOwnedData == null) { 140 // return (mError=NO_MEMORY); 141 // } 142 //// memcpy(mOwnedData, data, size); 143 // data = mOwnedData; 144 // } 145 146 // The size has been checked, so it is safe to read the data in the ResStringPool_header 147 // data structure. 148 mHeader = new ResStringPool_header(buf, offset); 149 150 // if (notDeviceEndian) { 151 // ResStringPool_header h = final_cast<ResStringPool_header*>(mHeader); 152 // h.header.headerSize = dtohs(mHeader.header.headerSize); 153 // h.header.type = dtohs(mHeader.header.type); 154 // h.header.size = dtohl(mHeader.header.size); 155 // h.stringCount = dtohl(mHeader.stringCount); 156 // h.styleCount = dtohl(mHeader.styleCount); 157 // h.flags = dtohl(mHeader.flags); 158 // h.stringsStart = dtohl(mHeader.stringsStart); 159 // h.stylesStart = dtohl(mHeader.stylesStart); 160 // } 161 162 if (mHeader.header.headerSize > mHeader.header.size 163 || mHeader.header.size > size) { 164 ALOGW("Bad string block: header size %d or total size %d is larger than data size %d\n", 165 (int)mHeader.header.headerSize, (int)mHeader.header.size, (int)size); 166 return (mError=BAD_TYPE); 167 } 168 mSize = mHeader.header.size; 169 mEntries = new IntArray(mHeader.myBuf(), mHeader.myOffset() + mHeader.header.headerSize); 170 171 if (mHeader.stringCount > 0) { 172 if ((mHeader.stringCount*4 /*sizeof(uint32_t)*/ < mHeader.stringCount) // uint32 overflow? 173 || (mHeader.header.headerSize+(mHeader.stringCount*4 /*sizeof(uint32_t)*/)) 174 > size) { 175 ALOGW("Bad string block: entry of %d items extends past data size %d\n", 176 (int)(mHeader.header.headerSize+(mHeader.stringCount*4/*sizeof(uint32_t)*/)), 177 (int)size); 178 return (mError=BAD_TYPE); 179 } 180 181 int charSize; 182 if (isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG)) { 183 charSize = 1 /*sizeof(uint8_t)*/; 184 } else { 185 charSize = 2 /*sizeof(uint16_t)*/; 186 } 187 188 // There should be at least space for the smallest string 189 // (2 bytes length, null terminator). 190 if (mHeader.stringsStart >= (mSize - 2 /*sizeof(uint16_t)*/)) { 191 ALOGW("Bad string block: string pool starts at %d, after total size %d\n", 192 (int)mHeader.stringsStart, (int)mHeader.header.size); 193 return (mError=BAD_TYPE); 194 } 195 196 mStrings = mHeader.stringsStart; 197 198 if (mHeader.styleCount == 0) { 199 mStringPoolSize = (mSize - mHeader.stringsStart) / charSize; 200 } else { 201 // check invariant: styles starts before end of data 202 if (mHeader.stylesStart >= (mSize - 2 /*sizeof(uint16_t)*/)) { 203 ALOGW("Bad style block: style block starts at %d past data size of %d\n", 204 (int)mHeader.stylesStart, (int)mHeader.header.size); 205 return (mError=BAD_TYPE); 206 } 207 // check invariant: styles follow the strings 208 if (mHeader.stylesStart <= mHeader.stringsStart) { 209 ALOGW("Bad style block: style block starts at %d, before strings at %d\n", 210 (int)mHeader.stylesStart, (int)mHeader.stringsStart); 211 return (mError=BAD_TYPE); 212 } 213 mStringPoolSize = 214 (mHeader.stylesStart-mHeader.stringsStart)/charSize; 215 } 216 217 // check invariant: stringCount > 0 requires a string pool to exist 218 if (mStringPoolSize == 0) { 219 ALOGW("Bad string block: stringCount is %d but pool size is 0\n", (int)mHeader.stringCount); 220 return (mError=BAD_TYPE); 221 } 222 223 // if (notDeviceEndian) { 224 // int i; 225 // uint32_t* e = final_cast<uint32_t*>(mEntries); 226 // for (i=0; i<mHeader.stringCount; i++) { 227 // e[i] = dtohl(mEntries[i]); 228 // } 229 // if (!(mHeader.flags&ResStringPool_header::UTF8_FLAG)) { 230 // final uint16_t* strings = (final uint16_t*)mStrings; 231 // uint16_t* s = final_cast<uint16_t*>(strings); 232 // for (i=0; i<mStringPoolSize; i++) { 233 // s[i] = dtohs(strings[i]); 234 // } 235 // } 236 // } 237 238 // if ((mHeader->flags&ResStringPool_header::UTF8_FLAG && 239 // ((uint8_t*)mStrings)[mStringPoolSize-1] != 0) || 240 // (!(mHeader->flags&ResStringPool_header::UTF8_FLAG) && 241 // ((uint16_t*)mStrings)[mStringPoolSize-1] != 0)) { 242 243 if ((isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG) 244 && (mHeader.getByte(mStrings + mStringPoolSize - 1) != 0)) 245 || (!isTruthy(mHeader.flags & ResStringPool_header.UTF8_FLAG) 246 && (mHeader.getShort(mStrings + mStringPoolSize * 2 - 2) != 0))) { 247 ALOGW("Bad string block: last string is not 0-terminated\n"); 248 return (mError=BAD_TYPE); 249 } 250 } else { 251 mStrings = -1; 252 mStringPoolSize = 0; 253 } 254 255 if (mHeader.styleCount > 0) { 256 mEntryStyles = new IntArray(mEntries.myBuf(), mEntries.myOffset() + mHeader.stringCount * SIZEOF_INT); 257 // invariant: integer overflow in calculating mEntryStyles 258 if (mEntryStyles.myOffset() < mEntries.myOffset()) { 259 ALOGW("Bad string block: integer overflow finding styles\n"); 260 return (mError=BAD_TYPE); 261 } 262 263 // if (((const uint8_t*)mEntryStyles-(const uint8_t*)mHeader) > (int)size) { 264 if ((mEntryStyles.myOffset() - mHeader.myOffset()) > (int)size) { 265 ALOGW("Bad string block: entry of %d styles extends past data size %d\n", 266 (int)(mEntryStyles.myOffset()), 267 (int)size); 268 return (mError=BAD_TYPE); 269 } 270 mStyles = mHeader.stylesStart; 271 if (mHeader.stylesStart >= mHeader.header.size) { 272 ALOGW("Bad string block: style pool starts %d, after total size %d\n", 273 (int)mHeader.stylesStart, (int)mHeader.header.size); 274 return (mError=BAD_TYPE); 275 } 276 mStylePoolSize = 277 (mHeader.header.size-mHeader.stylesStart) /* / sizeof(uint32_t)*/; 278 279 // if (notDeviceEndian) { 280 // size_t i; 281 // uint32_t* e = final_cast<uint32_t*>(mEntryStyles); 282 // for (i=0; i<mHeader.styleCount; i++) { 283 // e[i] = dtohl(mEntryStyles[i]); 284 // } 285 // uint32_t* s = final_cast<uint32_t*>(mStyles); 286 // for (i=0; i<mStylePoolSize; i++) { 287 // s[i] = dtohl(mStyles[i]); 288 // } 289 // } 290 291 // final ResStringPool_span endSpan = { 292 // { htodl(ResStringPool_span.END) }, 293 // htodl(ResStringPool_span.END), htodl(ResStringPool_span.END) 294 // }; 295 // if (memcmp(&mStyles[mStylePoolSize-(sizeof(endSpan)/sizeof(uint32_t))], 296 // &endSpan, sizeof(endSpan)) != 0) { 297 ResStringPool_span endSpan = new ResStringPool_span(buf, 298 mHeader.myOffset() + mStyles + (mStylePoolSize - ResStringPool_span.SIZEOF /* / 4 */)); 299 if (!endSpan.isEnd()) { 300 ALOGW("Bad string block: last style is not 0xFFFFFFFF-terminated\n"); 301 return (mError=BAD_TYPE); 302 } 303 } else { 304 mEntryStyles = null; 305 mStyles = 0; 306 mStylePoolSize = 0; 307 } 308 309 return (mError=NO_ERROR); 310 } 311 312 // public void setTo(XmlResStringPool xmlStringPool) { 313 // this.mHeader = new ResStringPoolHeader(); 314 // this.mStrings = new ArrayList<>(); 315 // Collections.addAll(mStrings, xmlStringPool.strings()); 316 // } 317 setError(int error)318 private int setError(int error) { 319 mError = error; 320 return mError; 321 } 322 uninit()323 void uninit() { 324 setError(NO_INIT); 325 mHeader = null; 326 } 327 stringAt(int idx)328 public String stringAt(int idx) { 329 if (mError == NO_ERROR && idx < mHeader.stringCount) { 330 final boolean isUTF8 = (mHeader.flags&ResStringPool_header.UTF8_FLAG) != 0; 331 // const uint32_t off = mEntries[idx]/(isUTF8?sizeof(uint8_t):sizeof(uint16_t)); 332 ByteBuffer buf = mHeader.myBuf(); 333 int bufOffset = mHeader.myOffset(); 334 // const uint32_t off = mEntries[idx]/(isUTF8?sizeof(uint8_t):sizeof(uint16_t)); 335 final int off = mEntries.get(idx) 336 /(isUTF8?1/*sizeof(uint8_t)*/:2/*sizeof(uint16_t)*/); 337 if (off < (mStringPoolSize-1)) { 338 if (!isUTF8) { 339 final int strings = mStrings; 340 final int str = strings+off*2; 341 return decodeString(buf, bufOffset + str, Type.UTF16); 342 // int u16len = decodeLengthUTF16(buf, bufOffset + str); 343 // if ((str+u16len*2-strings) < mStringPoolSize) { 344 // // Reject malformed (non null-terminated) strings 345 // if (buf.getShort(bufOffset + str + u16len*2) != 0x0000) { 346 // ALOGW("Bad string block: string #%d is not null-terminated", 347 // (int)idx); 348 // return null; 349 // } 350 // byte[] bytes = new byte[u16len * 2]; 351 // buf.position(bufOffset + str); 352 // buf.get(bytes); 353 // // Reject malformed (non null-terminated) strings 354 // if (str[encLen] != 0x00) { 355 // ALOGW("Bad string block: string #%d is not null-terminated", 356 // (int)idx); 357 // return NULL; 358 // } 359 // return new String(bytes, StandardCharsets.UTF_16); 360 // } else { 361 // ALOGW("Bad string block: string #%d extends to %d, past end at %d\n", 362 // (int)idx, (int)(str+u16len-strings), (int)mStringPoolSize); 363 // } 364 } else { 365 final int strings = mStrings; 366 final int u8str = strings+off; 367 return decodeString(buf, bufOffset + u8str, Type.UTF8); 368 369 // *u16len = decodeLength(&u8str); 370 // size_t u8len = decodeLength(&u8str); 371 // 372 // // encLen must be less than 0x7FFF due to encoding. 373 // if ((uint32_t)(u8str+u8len-strings) < mStringPoolSize) { 374 // AutoMutex lock(mDecodeLock); 375 // 376 // if (mCache != NULL && mCache[idx] != NULL) { 377 // return mCache[idx]; 378 // } 379 // 380 // // Retrieve the actual length of the utf8 string if the 381 // // encoded length was truncated 382 // if (stringDecodeAt(idx, u8str, u8len, &u8len) == NULL) { 383 // return NULL; 384 // } 385 // 386 // // Since AAPT truncated lengths longer than 0x7FFF, check 387 // // that the bits that remain after truncation at least match 388 // // the bits of the actual length 389 // ssize_t actualLen = utf8_to_utf16_length(u8str, u8len); 390 // if (actualLen < 0 || ((size_t)actualLen & 0x7FFF) != *u16len) { 391 // ALOGW("Bad string block: string #%lld decoded length is not correct " 392 // "%lld vs %llu\n", 393 // (long long)idx, (long long)actualLen, (long long)*u16len); 394 // return NULL; 395 // } 396 // 397 // utf8_to_utf16(u8str, u8len, u16str, *u16len + 1); 398 // 399 // if (mCache == NULL) { 400 // #ifndef __ANDROID__ 401 // if (kDebugStringPoolNoisy) { 402 // ALOGI("CREATING STRING CACHE OF %zu bytes", 403 // mHeader->stringCount*sizeof(char16_t**)); 404 // } 405 // #else 406 // // We do not want to be in this case when actually running Android. 407 // ALOGW("CREATING STRING CACHE OF %zu bytes", 408 // static_cast<size_t>(mHeader->stringCount*sizeof(char16_t**))); 409 // #endif 410 // mCache = (char16_t**)calloc(mHeader->stringCount, sizeof(char16_t*)); 411 // if (mCache == NULL) { 412 // ALOGW("No memory trying to allocate decode cache table of %d bytes\n", 413 // (int)(mHeader->stringCount*sizeof(char16_t**))); 414 // return NULL; 415 // } 416 // } 417 // *u16len = (size_t) actualLen; 418 // char16_t *u16str = (char16_t *)calloc(*u16len+1, sizeof(char16_t)); 419 // if (!u16str) { 420 // ALOGW("No memory when trying to allocate decode cache for string #%d\n", 421 // (int)idx); 422 // return NULL; 423 // } 424 // 425 // if (kDebugStringPoolNoisy) { 426 // ALOGI("Caching UTF8 string: %s", u8str); 427 // } 428 // 429 // mCache[idx] = u16str; 430 // return u16str; 431 // } else { 432 // ALOGW("Bad string block: string #%lld extends to %lld, past end at %lld\n", 433 // (long long)idx, (long long)(u8str+u8len-strings), 434 // (long long)mStringPoolSize); 435 // } 436 } 437 } else { 438 ALOGW("Bad string block: string #%d entry is at %d, past end at %d\n", 439 (int)idx, (int)(off*2/*sizeof(uint16_t)*/), 440 (int)(mStringPoolSize*2/*sizeof(uint16_t)*/)); 441 } 442 } 443 return null; 444 } 445 stringAt(int idx, Ref<Integer> outLen)446 String stringAt(int idx, Ref<Integer> outLen) { 447 String s = stringAt(idx); 448 if (s != null && outLen != null) { 449 outLen.set(s.length()); 450 } 451 return s; 452 } 453 string8At(int id, Ref<Integer> outLen)454 public String string8At(int id, Ref<Integer> outLen) { 455 return stringAt(id, outLen); 456 } 457 styleAt(final ResStringPool_ref ref)458 final ResStringPool_span styleAt(final ResStringPool_ref ref) { 459 return styleAt(ref.index); 460 } 461 styleAt(int idx)462 public final ResStringPool_span styleAt(int idx) { 463 if (mError == NO_ERROR && idx < mHeader.styleCount) { 464 // const uint32_t off = (mEntryStyles[idx]/sizeof(uint32_t)); 465 final int off = mEntryStyles.get(idx) / SIZEOF_INT; 466 if (off < mStylePoolSize) { 467 // return (const ResStringPool_span*)(mStyles+off); 468 return new ResStringPool_span( 469 mHeader.myBuf(), mHeader.myOffset() + mStyles + off * SIZEOF_INT); 470 } else { 471 ALOGW("Bad string block: style #%d entry is at %d, past end at %d\n", 472 (int)idx, (int)(off*SIZEOF_INT), 473 (int)(mStylePoolSize*SIZEOF_INT)); 474 } 475 } 476 return null; 477 } 478 indexOfString(String str)479 public int indexOfString(String str) { 480 if (mError != NO_ERROR) { 481 return mError; 482 } 483 484 if (kDebugStringPoolNoisy) { 485 ALOGI("indexOfString : %s", str); 486 } 487 488 if ( (mHeader.flags&ResStringPoolHeader.SORTED_FLAG) != 0) { 489 // Do a binary search for the string... this is a little tricky, 490 // because the strings are sorted with strzcmp16(). So to match 491 // the ordering, we need to convert strings in the pool to UTF-16. 492 // But we don't want to hit the cache, so instead we will have a 493 // local temporary allocation for the conversions. 494 int l = 0; 495 int h = mHeader.stringCount-1; 496 497 int mid; 498 while (l <= h) { 499 mid = l + (h - l)/2; 500 String s = stringAt(mid); 501 int c = s != null ? s.compareTo(str) : -1; 502 if (kDebugStringPoolNoisy) { 503 ALOGI("Looking at %s, cmp=%d, l/mid/h=%d/%d/%d\n", 504 s, c, (int)l, (int)mid, (int)h); 505 } 506 if (c == 0) { 507 if (kDebugStringPoolNoisy) { 508 ALOGI("MATCH!"); 509 } 510 return mid; 511 } else if (c < 0) { 512 l = mid + 1; 513 } else { 514 h = mid - 1; 515 } 516 } 517 } else { 518 // It is unusual to get the ID from an unsorted string block... 519 // most often this happens because we want to get IDs for style 520 // span tags; since those always appear at the end of the string 521 // block, start searching at the back. 522 for (int i = mHeader.stringCount; i>=0; i--) { 523 String s = stringAt(i); 524 if (kDebugStringPoolNoisy) { 525 ALOGI("Looking at %s, i=%d\n", s, i); 526 } 527 if (Objects.equals(s, str)) { 528 if (kDebugStringPoolNoisy) { 529 ALOGI("MATCH!"); 530 } 531 return i; 532 } 533 } 534 } 535 536 return NAME_NOT_FOUND; 537 } 538 // size()539 public int size() { 540 return mError == NO_ERROR ? mHeader.stringCount : 0; 541 } 542 styleCount()543 int styleCount() { 544 return mError == NO_ERROR ? mHeader.styleCount : 0; 545 } 546 bytes()547 int bytes() { 548 return mError == NO_ERROR ? mHeader.header.size : 0; 549 } 550 isUTF8()551 public boolean isUTF8() { 552 return true; 553 } 554 getError()555 public int getError() { 556 return mError; 557 } 558 559 // int styleCount() final; 560 // int bytes() final; 561 // 562 // boolean isSorted() final; 563 // boolean isUTF8() final; 564 // 565 566 } 567